This commit is contained in:
tt 2024-10-21 11:21:34 +08:00
commit e937d047ce
92 changed files with 5389 additions and 1280 deletions

View File

@ -15,6 +15,12 @@
"test:ci": "npm run lint && npm run test:unit" "test:ci": "npm run lint && npm run test:unit"
}, },
"dependencies": { "dependencies": {
"@fullcalendar/core": "^6.1.15",
"@fullcalendar/daygrid": "^6.1.15",
"@fullcalendar/interaction": "^6.1.15",
"@fullcalendar/list": "^6.1.15",
"@fullcalendar/timegrid": "^6.1.15",
"@fullcalendar/vue": "^6.1.15",
"@wangeditor/editor": "^5.1.23", "@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^1.0.2", "@wangeditor/editor-for-vue": "^1.0.2",
"axios": "0.18.1", "axios": "0.18.1",
@ -31,6 +37,7 @@
"jsonlint": "1.6.3", "jsonlint": "1.6.3",
"jszip": "3.2.1", "jszip": "3.2.1",
"left-pad": "^1.3.0", "left-pad": "^1.3.0",
"moment": "^2.30.1",
"normalize.css": "7.0.0", "normalize.css": "7.0.0",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"path-to-regexp": "2.4.0", "path-to-regexp": "2.4.0",

16
admin/src/api/admin.js Normal file
View File

@ -0,0 +1,16 @@
import request from '@/utils/request'
// 获取路线列表
export function getProductsList() {
return request({
url: 'admin/products/list',
method: 'get'
})
}
export function addProducts(data) {
return request({
url: '/admin/products/add',
method: 'post',
data
})
}

View File

@ -7,6 +7,13 @@ export function orderBack(data) {
data data
}) })
} }
export function orderbackBatch(data) {
return request({
url: 'admin/order/backBatch',
method: 'post',
data
})
}
// 获取qa详情 // 获取qa详情
export function getQaDetail(city_id) { export function getQaDetail(city_id) {
return request({ return request({

View File

@ -0,0 +1,41 @@
import request from '@/utils/request'
//直播间列表
export function liveroom() {
return request({
url: '/admin/liveroom/index',
method: 'get'
})
}
// 排班列表
export function roomWorks(live_room_id) {
return request({
url: '/admin/liveroom/roomWorks',
method: 'get',
params: { live_room_id }
})
}
// 可排班主播列表
export function availableZhubo(params) {
return request({
url: '/admin/liveroom/availableZhubo',
method: 'get',
params: params
})
}
// 编辑直播间
export function saveProducts(data) {
return request({
url: '/admin/liveroom/saveProducts',
method: 'post',
data
})
}
// 保存排班信息
export function saveRoomWorks(data) {
return request({
url: '/admin/liveroom/saveRoomWorks',
method: 'post',
data
})
}

View File

@ -1,11 +1,16 @@
<template> <template>
<div class="navbar"> <div class="navbar">
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> <hamburger
id="hamburger-container"
:is-active="sidebar.opened"
class="hamburger-container"
@toggleClick="toggleSideBar"
/>
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" /> <breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
<div class="right-menu"> <div class="right-menu">
<template v-if="device!=='mobile'"> <template v-if="device !== 'mobile'">
<!-- <search id="header-search" class="right-menu-item" /> --> <!-- <search id="header-search" class="right-menu-item" /> -->
<!-- <screenfull id="screenfull" class="right-menu-item hover-effect" /> <!-- <screenfull id="screenfull" class="right-menu-item hover-effect" />
@ -13,23 +18,54 @@
<el-tooltip content="Global Size" effect="dark" placement="bottom"> <el-tooltip content="Global Size" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" /> <size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip> --> </el-tooltip> -->
</template> </template>
<!-- <div class="right-menu-item hover-effect"> <!-- <div class="right-menu-item hover-effect">
<el-button @click="drawer = true">QA常见问题</el-button> <el-button @click="drawer = true">QA常见问题</el-button>
</div>--> </div>-->
<div v-if="appointment_num" class="right-menu-item hover-effect">
<el-button style="color: red" @click="handleAppointemnet()"
>预约待处理({{ appointment_num }})</el-button
>
</div>
<div v-if="$store.getters.is_anchor" class="right-menu-item hover-effect"> <div v-if="$store.getters.is_anchor" class="right-menu-item hover-effect">
<el-button @click="dialogWorks = true">排班{{ $store.getters.name }}</el-button> <el-button @click="dialogWorks = true"
>排班{{ $store.getters.name }}</el-button
>
</div> </div>
<div class="right-menu-item hover-effect"> <div class="right-menu-item hover-effect">
<el-button :style="{backgroundColor:!workstatus?'#409EFF':'#fff',color:workstatus?'#979797':'#fff'}" @click="startWorks">{{ workstatus?'上班':'上班中' }}</el-button> <el-button
<el-button :style="{backgroundColor:workstatus?'#409EFF':'#fff',color:workstatus?'#fff':'#979797'}" @click="endWorks">{{ workstatus?'下班中':'下班' }}</el-button> :style="{
backgroundColor: !workstatus ? '#409EFF' : '#fff',
color: workstatus ? '#979797' : '#fff',
}"
@click="startWorks"
>{{ workstatus ? "上班" : "上班中" }}</el-button
>
<el-button
:style="{
backgroundColor: workstatus ? '#409EFF' : '#fff',
color: workstatus ? '#fff' : '#979797',
}"
@click="endWorks"
>{{ workstatus ? "下班中" : "下班" }}</el-button
>
</div> </div>
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click"> <el-dropdown
class="avatar-container right-menu-item hover-effect"
trigger="click"
>
<div class="avatar-wrapper"> <div class="avatar-wrapper">
<img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar" alt=""> <img
<i class="el-icon-camera" style="position: absolute;" @click.stop="showAvatar = true" /> :src="avatar + '?imageView2/1/w/80/h/80'"
class="user-avatar"
alt=""
/>
<i
class="el-icon-camera"
style="position: absolute"
@click.stop="showAvatar = true"
/>
<i class="el-icon-caret-bottom" /> <i class="el-icon-caret-bottom" />
</div> </div>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
@ -43,18 +79,12 @@
<el-dropdown-item>修改密码</el-dropdown-item> <el-dropdown-item>修改密码</el-dropdown-item>
</div> </div>
<el-dropdown-item divided @click.native="logout"> <el-dropdown-item divided @click.native="logout">
<span style="display:block;">退出登录</span> <span style="display: block">退出登录</span>
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
</div> </div>
<el-dialog <el-dialog title="提示" :visible.sync="showAvatar" width="30%" center>
title="提示"
:visible.sync="showAvatar"
width="30%"
center
>
<el-upload <el-upload
class="avatar-uploader" class="avatar-uploader"
action="/admin/index/avatar" action="/admin/index/avatar"
@ -62,10 +92,9 @@
:on-success="handleAvatarSuccess" :on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload" :before-upload="beforeAvatarUpload"
> >
<img v-if="imageUrl" :src="imageUrl" class="avatar" alt=""> <img v-if="imageUrl" :src="imageUrl" class="avatar" alt="" />
<i v-else class="el-icon-plus avatar-uploader-icon" /> <i v-else class="el-icon-plus avatar-uploader-icon" />
</el-upload> </el-upload>
</el-dialog> </el-dialog>
<el-dialog title="修改密码" :visible.sync="dialogPWD"> <el-dialog title="修改密码" :visible.sync="dialogPWD">
@ -85,7 +114,7 @@
<el-dialog title="排班" width="90%" :visible.sync="dialogWorks"> <el-dialog title="排班" width="90%" :visible.sync="dialogWorks">
<el-form :rules="rules"> <el-form :rules="rules">
<el-row style="margin-bottom: 10px;"> <el-row style="margin-bottom: 10px">
<el-col :span="24"> <el-col :span="24">
<el-date-picker <el-date-picker
v-model="times[0]" v-model="times[0]"
@ -95,7 +124,7 @@
/> />
</el-col> </el-col>
</el-row> </el-row>
<el-row style="margin-bottom: 10px;"> <el-row style="margin-bottom: 10px">
<el-col :span="24"> <el-col :span="24">
<el-date-picker <el-date-picker
v-model="times[1]" v-model="times[1]"
@ -105,10 +134,15 @@
/> />
</el-col> </el-col>
</el-row> </el-row>
<el-row style="margin-bottom: 10px;"> <el-row style="margin-bottom: 10px">
<el-col :span="24"> <el-col :span="24">
<el-checkbox-group v-model="os"> <el-checkbox-group v-model="os">
<el-checkbox v-for="(v,i,k) in $store.getters.oss" :key="k" :label="i">{{ v }}</el-checkbox> <el-checkbox
v-for="(v, i, k) in $store.getters.oss"
:key="k"
:label="i"
>{{ v }}</el-checkbox
>
</el-checkbox-group> </el-checkbox-group>
</el-col> </el-col>
</el-row> </el-row>
@ -126,10 +160,21 @@
:modal="false" :modal="false"
> >
<div v-clickoutside="handleClose" class="drawer"> <div v-clickoutside="handleClose" class="drawer">
<el-button v-if="QaShow" type="success" @click="drawer = false"> </el-button> <el-button v-if="QaShow" type="success" @click="drawer = false"
<el-button v-if="!QaShow" type="success" @click="QaShow = true"> </el-button> > </el-button
>
<el-button v-if="!QaShow" type="success" @click="QaShow = true"
> </el-button
>
<div v-if="QaShow" class="mod"> <div v-if="QaShow" class="mod">
<el-button v-for="item in getQaCityList" :key="item.city_id" style="width: 150px;" size="medium" type="primary" @click="clickQaList(item)"> <el-button
v-for="item in getQaCityList"
:key="item.city_id"
style="width: 150px"
size="medium"
type="primary"
@click="clickQaList(item)"
>
{{ item.city_name }} {{ item.city_name }}
</el-button> </el-button>
</div> </div>
@ -145,17 +190,17 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex' import { mapGetters } from "vuex";
import Breadcrumb from '@/components/Breadcrumb' import Breadcrumb from "@/components/Breadcrumb";
import Hamburger from '@/components/Hamburger' import Hamburger from "@/components/Hamburger";
import Screenfull from '@/components/Screenfull' import Screenfull from "@/components/Screenfull";
import SizeSelect from '@/components/SizeSelect' import SizeSelect from "@/components/SizeSelect";
import Search from '@/components/HeaderSearch' import Search from "@/components/HeaderSearch";
import { color } from 'echarts/lib/export' import { color } from "echarts/lib/export";
import sidebar from '@/layout/components/Sidebar/index.vue' import sidebar from "@/layout/components/Sidebar/index.vue";
import avatar from 'element-ui/packages/avatar' import avatar from "element-ui/packages/avatar";
import {getQaCityList, getQaList} from '@/api/qa' import { getQaCityList, getQaList } from "@/api/qa";
import clickoutside from 'element-ui/src/utils/clickoutside' import clickoutside from "element-ui/src/utils/clickoutside";
export default { export default {
directives: { clickoutside }, directives: { clickoutside },
components: { components: {
@ -164,20 +209,22 @@ export default {
Screenfull, Screenfull,
SizeSelect, SizeSelect,
// eslint-disable-next-line vue/no-unused-Wangeditor // eslint-disable-next-line vue/no-unused-Wangeditor
Search Search,
},
props: {
appointment_num: {
type: Number,
default: 0,
},
}, },
computed: { computed: {
avatar() { avatar() {
return avatar return avatar;
}, },
sidebar() { sidebar() {
return sidebar return sidebar;
}, },
...mapGetters([ ...mapGetters(["sidebar", "avatar", "device"]),
'sidebar',
'avatar',
'device'
])
}, },
// eslint-disable-next-line vue/order-in-Wangeditor // eslint-disable-next-line vue/order-in-Wangeditor
data() { data() {
@ -195,112 +242,143 @@ export default {
getQaLists: [], getQaLists: [],
times: [], times: [],
form: { form: {
oldpwd: '', oldpwd: "",
pwd: '' pwd: "",
}, },
QaInfo: { QaInfo: {
title: '', title: "",
content: '' content: "",
}, },
rules: { rules: {
oldpwd: [ oldpwd: [
{ required: true, message: '请输入旧密码', trigger: 'blur' }, { required: true, message: "请输入旧密码", trigger: "blur" },
{ min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' } {
min: 6,
max: 20,
message: "长度在 6 到 20 个字符",
trigger: "blur",
},
], ],
pwd: [ pwd: [
{ required: true, message: '请输入密码', trigger: 'blur' }, { required: true, message: "请输入密码", trigger: "blur" },
{ min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' } {
] min: 6,
} max: 20,
} message: "长度在 6 到 20 个字符",
trigger: "blur",
},
],
},
};
}, },
created() { created() {
this.getworkstatus() this.getworkstatus();
getQaCityList().then(res => { getQaCityList().then((res) => {
this.getQaCityList = res.data this.getQaCityList = res.data;
}) });
}, },
methods: { methods: {
color, color,
toggleSideBar() { toggleSideBar() {
this.$store.dispatch('app/toggleSideBar') this.$store.dispatch("app/toggleSideBar");
}, },
handleClose() { handleClose() {
this.drawer = false this.drawer = false;
this.QaShow = true this.QaShow = true;
}, },
clickQaList(data) { clickQaList(data) {
getQaList(data.city_id).then(res => { getQaList(data.city_id).then((res) => {
this.getQaLists = res.data this.getQaLists = res.data;
}) });
console.log(JSON.stringify(this.getQaLists)) console.log(JSON.stringify(this.getQaLists));
if (!this.getQaLists) { if (!this.getQaLists) {
return this.$message({ return this.$message({
message: '暂无QA问题', message: "暂无QA问题",
type: 'warning', type: "warning",
duration: 1500 duration: 1500,
}) });
} }
this.QaShow = false this.QaShow = false;
}, },
async logout() { async logout() {
await this.$store.dispatch('user/logout') await this.$store.dispatch("user/logout");
this.$router.push(`/login?redirect=${this.$route.fullPath}`) this.$router.push(`/login?redirect=${this.$route.fullPath}`);
}, },
handleAvatarSuccess(res, file) { handleAvatarSuccess(res, file) {
this.imageUrl = URL.createObjectURL(file.raw) this.imageUrl = URL.createObjectURL(file.raw);
}, },
pwd() { pwd() {
this.$axios.post('/admin/admin/pwd', this.form).then(res => { this.$axios
this.dialogPWD = false .post("/admin/admin/pwd", this.form)
this.form = {} .then((res) => {
}).catch(err => { this.dialogPWD = false;
console.log(err) this.form = {};
}) })
.catch((err) => {
console.log(err);
});
}, },
saveWork() { saveWork() {
this.$axios.post('/admin/work/save2', { times: this.times, os: this.os }).then(res => { this.$axios
console.log(res) .post("/admin/work/save2", { times: this.times, os: this.os })
this.$message({ .then((res) => {
showClose: true, console.log(res);
message: '添加成功' this.$message({
showClose: true,
message: "添加成功",
});
this.dialogWorks = false;
}) })
this.dialogWorks = false .catch((err) => {
}).catch(err => { console.log(err);
console.log(err) });
})
}, },
startWorks() { startWorks() {
this.$axios.post('/admin/admin/editInfo', { is_order: 1 }).then(res => { this.$axios
console.log(res) .post("/admin/admin/editInfo", { is_order: 1 })
this.$message({ .then((res) => {
showClose: true, console.log(res);
message: '上班成功' this.$message({
showClose: true,
message: "上班成功",
});
this.getworkstatus();
}) })
this.getworkstatus() .catch((err) => {
}).catch(err => { console.log(err);
console.log(err) });
})
}, },
endWorks() { endWorks() {
this.$axios.post('/admin/admin/editInfo', { is_order: 0 }).then(res => { this.$axios
console.log(res) .post("/admin/admin/editInfo", { is_order: 0 })
this.$message({ .then((res) => {
showClose: true, console.log(res);
message: '下班成功' this.$message({
showClose: true,
message: "下班成功",
});
this.getworkstatus();
}) })
this.getworkstatus() .catch((err) => {
}).catch(err => { console.log(err);
console.log(err) });
})
}, },
getworkstatus() { getworkstatus() {
this.$axios.post('/admin/work/getworkstatus', { id: this.id }).then(res => { this.$axios
console.log(res) .post("/admin/work/getworkstatus", { id: this.id })
this.workstatus = res.data .then((res) => {
}).catch(err => { console.log(res);
console.log(err) this.workstatus = res.data;
}) })
.catch((err) => {
console.log(err);
});
},
handleAppointemnet() {
this.$router.push({
path: "/order/index/",
query: { appointment_status: "1", refresh: Date.now() },
});
}, },
beforeAvatarUpload(file) { beforeAvatarUpload(file) {
/*const isJPG = file.type === 'image/jpeg' /*const isJPG = file.type === 'image/jpeg'
@ -313,29 +391,29 @@ export default {
this.$message.error('上传头像图片大小不能超过 2MB!') this.$message.error('上传头像图片大小不能超过 2MB!')
} }
return isJPG && isLt2M*/ return isJPG && isLt2M*/
} },
} },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.drawer{ .drawer {
padding: 20px 0 0 20px; padding: 20px 0 0 20px;
width: 100%; width: 100%;
height: 100vh; height: 100vh;
} }
.mod{ .mod {
margin-top: 50px; margin-top: 50px;
} }
.ver{ .ver {
margin-top: 30px; margin-top: 30px;
color: #6c6f71; color: #6c6f71;
&_title{ &_title {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
margin-bottom: 12px; margin-bottom: 12px;
} }
&_content{ &_content {
line-height: 26px; line-height: 26px;
font-size: 15px; font-size: 15px;
padding-bottom: 10px; padding-bottom: 10px;
@ -348,18 +426,18 @@ export default {
overflow: hidden; overflow: hidden;
position: relative; position: relative;
background: #fff; background: #fff;
box-shadow: 0 1px 4px rgba(0,21,41,.08); box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
.hamburger-container { .hamburger-container {
line-height: 46px; line-height: 46px;
height: 100%; height: 100%;
float: left; float: left;
cursor: pointer; cursor: pointer;
transition: background .3s; transition: background 0.3s;
-webkit-tap-highlight-color:transparent; -webkit-tap-highlight-color: transparent;
&:hover { &:hover {
background: rgba(0, 0, 0, .025) background: rgba(0, 0, 0, 0.025);
} }
} }
@ -391,10 +469,10 @@ export default {
&.hover-effect { &.hover-effect {
cursor: pointer; cursor: pointer;
transition: background .3s; transition: background 0.3s;
&:hover { &:hover {
background: rgba(0, 0, 0, .025) background: rgba(0, 0, 0, 0.025);
} }
} }
} }
@ -433,7 +511,7 @@ export default {
overflow: hidden; overflow: hidden;
} }
.avatar-uploader .el-upload:hover { .avatar-uploader .el-upload:hover {
border-color: #409EFF; border-color: #409eff;
} }
.avatar-uploader-icon { .avatar-uploader-icon {
border: 1px solid #979797; border: 1px solid #979797;

View File

@ -1,16 +1,37 @@
<template> <template>
<div v-if="!item.hidden"> <div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow"> <template
v-if="
hasOneShowingChild(item.children, item) &&
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
!item.alwaysShow
"
>
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)"> <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}"> <el-menu-item
<item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" /> :index="resolvePath(onlyOneChild.path)"
:class="{ 'submenu-title-noDropdown': !isNest }"
>
<item
:icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"
:title="onlyOneChild.meta.title"
/>
</el-menu-item> </el-menu-item>
</app-link> </app-link>
</template> </template>
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body> <el-submenu
v-else
ref="subMenu"
:index="resolvePath(item.path)"
popper-append-to-body
>
<template slot="title"> <template slot="title">
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" /> <item
v-if="item.meta"
:icon="item.meta && item.meta.icon"
:title="item.meta.title"
/>
</template> </template>
<sidebar-item <sidebar-item
v-for="child in item.children" v-for="child in item.children"
@ -25,71 +46,72 @@
</template> </template>
<script> <script>
import path from 'path' import path from "path";
import { isExternal } from '@/utils/validate' import { isExternal } from "@/utils/validate";
import Item from './Item' import Item from "./Item";
import AppLink from './Link' import AppLink from "./Link";
import FixiOSBug from './FixiOSBug' import FixiOSBug from "./FixiOSBug";
export default { export default {
name: 'SidebarItem', name: "SidebarItem",
components: { Item, AppLink }, components: { Item, AppLink },
mixins: [FixiOSBug], mixins: [FixiOSBug],
props: { props: {
// route object // route object
item: { item: {
type: Object, type: Object,
required: true required: true,
}, },
isNest: { isNest: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
basePath: { basePath: {
type: String, type: String,
default: '' default: "",
} },
}, },
data() { data() {
// To fix https://github.com/PanJiaChen/vue-admin-template/issues/237 // To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
// TODO: refactor with render function // TODO: refactor with render function
this.onlyOneChild = null this.onlyOneChild = null;
return {} return {};
}, },
methods: { methods: {
hasOneShowingChild(children = [], parent) { hasOneShowingChild(children = [], parent) {
const showingChildren = children.filter(item => { const showingChildren = children.filter((item) => {
if (item.hidden) { if (item.hidden) {
return false return false;
} else { } else {
// Temp set(will be used if only has one showing child) // Temp set(will be used if only has one showing child)
this.onlyOneChild = item this.onlyOneChild = item;
return true return true;
} }
}) });
// When there is only one child router, the child router is displayed by default // When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) { if (showingChildren.length === 1) {
return true return true;
} }
// Show parent if there are no child router to display // Show parent if there are no child router to display
if (showingChildren.length === 0) { if (showingChildren.length === 0) {
this.onlyOneChild = { ... parent, path: '', noShowingChildren: true } this.onlyOneChild = { ...parent, path: "", noShowingChildren: true };
return true return true;
} }
return false return false;
}, },
resolvePath(routePath) { resolvePath(routePath) {
routePath = routePath.replace(/\/:[a-zA-Z]+/g, "");
if (isExternal(routePath)) { if (isExternal(routePath)) {
return routePath return routePath;
} }
if (isExternal(this.basePath)) { if (isExternal(this.basePath)) {
return this.basePath return this.basePath;
} }
return path.resolve(this.basePath, routePath) return path.resolve(this.basePath, routePath);
} },
} },
} };
</script> </script>

View File

@ -1,10 +1,14 @@
<template> <template>
<div :class="classObj" class="app-wrapper"> <div :class="classObj" class="app-wrapper">
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" /> <div
v-if="device === 'mobile' && sidebar.opened"
class="drawer-bg"
@click="handleClickOutside"
/>
<sidebar class="sidebar-container" /> <sidebar class="sidebar-container" />
<div :class="{hasTagsView:needTagsView}" class="main-container"> <div :class="{ hasTagsView: needTagsView }" class="main-container">
<div :class="{'fixed-header':fixedHeader}"> <div :class="{ 'fixed-header': fixedHeader }">
<navbar /> <navbar :appointment_num="appointment_num" />
<tags-view v-if="needTagsView" /> <tags-view v-if="needTagsView" />
</div> </div>
<app-main /> <app-main />
@ -16,127 +20,193 @@
</template> </template>
<script> <script>
import RightPanel from '@/components/RightPanel' import RightPanel from "@/components/RightPanel";
import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components' import { AppMain, Navbar, Settings, Sidebar, TagsView } from "./components";
import ResizeMixin from './mixin/ResizeHandler' import ResizeMixin from "./mixin/ResizeHandler";
import { mapState } from 'vuex' import { mapState } from "vuex";
import { getToken } from '@/utils/auth' import { getToken } from "@/utils/auth";
export default { export default {
name: 'Layout', name: "Layout",
components: { components: {
AppMain, AppMain,
Navbar, Navbar,
RightPanel, RightPanel,
Settings, Settings,
Sidebar, Sidebar,
TagsView TagsView,
}, },
mixins: [ResizeMixin], mixins: [ResizeMixin],
computed: { computed: {
...mapState({ ...mapState({
sidebar: state => state.app.sidebar, sidebar: (state) => state.app.sidebar,
device: state => state.app.device, device: (state) => state.app.device,
showSettings: state => state.settings.showSettings, showSettings: (state) => state.settings.showSettings,
needTagsView: state => state.settings.tagsView, needTagsView: (state) => state.settings.tagsView,
fixedHeader: state => state.settings.fixedHeader fixedHeader: (state) => state.settings.fixedHeader,
}), }),
classObj() { classObj() {
return { return {
hideSidebar: !this.sidebar.opened, hideSidebar: !this.sidebar.opened,
openSidebar: this.sidebar.opened, openSidebar: this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation, withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device === 'mobile' mobile: this.device === "mobile",
} };
} },
}, },
data(){ data() {
return { return {
iswork:null iswork: null,
} appointment_num: 0,
};
}, },
created(){ created() {
this.iswork = setInterval(()=>{ this.iswork = setInterval(() => {
let toke = getToken() let toke = getToken();
if(!toke || toke.length <= 0) return; if (!toke || toke.length <= 0) return;
this.$axios.get('/admin/index/iswork').then(res=>{ this.$axios
if(res && res.data.new > 0) { .get("/admin/index/iswork")
this.$notify({ .then((res) => {
title: '新的订单提醒', // console.log(res);
duration: 0,
dangerouslyUseHTMLString: true, this.appointment_num = res.data.appointment_num;
message: '<strong>你有('+res.data.new+')个新的订单,需要处理</strong>' console.log(this.appointment_num);
});
} if (res && res.data.new > 0) {
if(res && res.data.follow > 0) { this.$notify({
this.$notify({ title: "新的订单提醒",
title: '新的跟进提醒', duration: 0,
duration: 10000, dangerouslyUseHTMLString: true,
dangerouslyUseHTMLString: true, message:
message: '<strong>你有('+res.data.follow+')个跟进订单,需要处理</strong>' "<strong>你有(" +
}); res.data.new +
} ")个新的订单,需要处理</strong>",
if(res && res.data.back > 0) { });
this.$notify({ }
title: '转单申请', if (res && res.data.follow > 0) {
duration: 10000, this.$notify({
dangerouslyUseHTMLString: true, title: "新的跟进提醒",
message: '<strong>你有('+res.data.back+')个转单订单,需要处理</strong>' duration: 10000,
}); dangerouslyUseHTMLString: true,
} message:
}).catch(err=>{ "<strong>你有(" +
console.log(err) res.data.follow +
}) ")个跟进订单,需要处理</strong>",
},30000); });
}
if (res && res.data.back > 0) {
this.$notify({
title: "转单申请",
duration: 10000,
dangerouslyUseHTMLString: true,
message:
"<strong>你有(" +
res.data.back +
")个转单订单,需要处理</strong>",
});
}
if (res && res.data.order_write_off > 0) {
this.$notify({
title: "订单核销",
duration: 10000,
dangerouslyUseHTMLString: true,
message:
"<strong>你有(" +
res.data.order_write_off +
")个核销订单</strong>",
});
}
if (res && res.data.follow_message > 0) {
this.$notify({
// res.data.follow_order_id
title: "跟进提醒",
duration: 50000,
dangerouslyUseHTMLString: true,
message:
"<strong>你有(" +
res.data.follow_message +
")个跟进提醒</strong>",
onClick: () => {
// console.log(res.data.follow_order_id);
this.$router.push({
path: "/order/index/",
query: { id: res.data.follow_order_id, refresh: Date.now() },
});
},
});
}
})
.catch((err) => {
console.log(err);
});
}, 30000);
}, },
methods: { methods: {
handleClickOutside() { handleClickOutside() {
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false }) this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
} },
} followDetail(orderId) {
} console.log(orderId);
// this.$router.push({ path: "/order/index/" + orderId });
},
onInfo(item) {
this.value = null;
this.next_follow = null;
this.$set(item, "next_follow", null);
this.item = item;
this.active = "follow";
this.$axios
.get("/admin/order/info", { params: { id: item.id } })
.then((res) => {
this.item = res.data;
this.dialogVisible = true;
})
.catch((err) => {});
},
},
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "~@/styles/mixin.scss"; @import "~@/styles/mixin.scss";
@import "~@/styles/variables.scss"; @import "~@/styles/variables.scss";
.app-wrapper { .app-wrapper {
@include clearfix; @include clearfix;
position: relative; position: relative;
height: 100%; height: 100%;
width: 100%; width: 100%;
&.mobile.openSidebar { &.mobile.openSidebar {
position: fixed;
top: 0;
}
}
.drawer-bg {
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
}
.fixed-header {
position: fixed; position: fixed;
top: 0; top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$sideBarWidth});
transition: width 0.28s;
} }
}
.hideSidebar .fixed-header { .drawer-bg {
width: calc(100% - 54px) background: #000;
} opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
}
.mobile .fixed-header { .fixed-header {
width: 100%; position: fixed;
} top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$sideBarWidth});
transition: width 0.28s;
}
.hideSidebar .fixed-header {
width: calc(100% - 54px);
}
.mobile .fixed-header {
width: 100%;
}
</style> </style>

View File

@ -3,7 +3,7 @@ import Vue from 'vue'
import Cookies from 'js-cookie' import Cookies from 'js-cookie'
import 'normalize.css/normalize.css' // a modern alternative to CSS resets import 'normalize.css/normalize.css' // a modern alternative to CSS resets
// import consts from '@/utils/consts'
import Element from 'element-ui' import Element from 'element-ui'
import './styles/element-variables.scss' import './styles/element-variables.scss'
// import enLang from 'element-ui/lib/locale/lang/en'// 如果使用中文语言包请默认支持,无需额外引入,请删除该依赖 // import enLang from 'element-ui/lib/locale/lang/en'// 如果使用中文语言包请默认支持,无需额外引入,请删除该依赖
@ -36,7 +36,7 @@ if (process.env.NODE_ENV === 'production') {
const { mockXHR } = require('../mock') const { mockXHR } = require('../mock')
mockXHR() mockXHR()
} }
// Vue.prototype.$consts = consts
Vue.use(Element, { Vue.use(Element, {
size: Cookies.get('size') || 'medium' // set element-ui default size size: Cookies.get('size') || 'medium' // set element-ui default size
// locale: enLang // 如果使用中文,无需设置,请删除 // locale: enLang // 如果使用中文,无需设置,请删除

View File

@ -103,6 +103,15 @@ export const asyncRoutes = [
roles: ['admin'] roles: ['admin']
} }
}, },
{
path: 'scheduling',
component: () => import('@/views/scheduling/index'),
name: 'scheduling',
meta: {
title: '直播排班',
roles: ['admin']
}
},
{ {
path: 'onlines', path: 'onlines',
component: () => import('@/views/onlines/online.vue'), component: () => import('@/views/onlines/online.vue'),
@ -189,6 +198,24 @@ export const asyncRoutes = [
roles: ['order_back', 'editor'] roles: ['order_back', 'editor']
} }
}, },
{
path: 'refunded',
component: () => import('@/views/order/refunded'),
name: 'OrderBack',
meta: {
title: '已退款订单',
roles: ['order_back', 'editor']
}
},
{
path: 'appointment',
component: () => import('@/views/order/appointment'),
name: 'appointment',
meta: {
title: '预约记录',
roles: ['order_back', 'editor']
}
},
] ]
}, },
{ {

View File

@ -1,7 +1,7 @@
import axios from 'axios' import axios from 'axios'
import { MessageBox, Message } from 'element-ui' import { MessageBox, Message } from 'element-ui'
import store from '@/store' import store from '@/store'
import { getToken } from '@/utils/auth' import { getToken,removeToken } from '@/utils/auth'
// create an axios instance // create an axios instance
const service = axios.create({ const service = axios.create({
@ -65,6 +65,9 @@ service.interceptors.response.use(
}) })
}) })
} }
if (res.code == 400) {
removeToken()
}
return Promise.reject(new Error(res.message || 'Error')) return Promise.reject(new Error(res.message || 'Error'))
} else { } else {
return res return res

View File

@ -1,93 +1,197 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<div class="filter-container"> <div class="filter-container">
<el-input v-model="listQuery.username" placeholder="用户名" style="width: 200px; margin-right: 10px;" class="filter-item" /> <el-input
<el-button class="filter-item" type="primary" icon="el-icon-search" @click="getList"> v-model="listQuery.username"
placeholder="用户名"
style="width: 200px; margin-right: 10px"
class="filter-item"
/>
<el-button
class="filter-item"
type="primary"
icon="el-icon-search"
@click="getList"
>
搜索 搜索
</el-button> </el-button>
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-circle-plus" @click="handleCreate({routes:[]})"> <el-button
class="filter-item"
style="margin-left: 10px"
type="primary"
icon="el-icon-circle-plus"
@click="handleCreate({ routes: [] })"
>
添加 添加
</el-button> </el-button>
</div> </div>
<el-table v-loading="listLoading" :data="list" border fit highlight-current-row style="width: 100%"> <el-table
v-loading="listLoading"
:data="list"
border
fit
highlight-current-row
style="width: 100%"
>
<el-table-column align="center" label="操作" width="200"> <el-table-column align="center" label="操作" width="200">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="primary" @click="handleCreate(scope.row)" size="small" icon="el-icon-edit"> <el-button
type="primary"
@click="handleCreate(scope.row)"
size="small"
icon="el-icon-edit"
>
修改 修改
</el-button> </el-button>
<el-button type="primary" v-if="scope.row.status" @click="onWork(scope.row)" size="small" icon="el-icon-date"> <el-button
type="primary"
v-if="scope.row.status"
@click="onWork(scope.row)"
size="small"
icon="el-icon-date"
>
排班 排班
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="ID" width="80" prop="id"></el-table-column> <el-table-column
align="center"
label="ID"
width="80"
prop="id"
></el-table-column>
<el-table-column align="center" label="用户名" width="160" prop="username"></el-table-column> <el-table-column
align="center"
label="用户名"
width="160"
prop="username"
></el-table-column>
<el-table-column align="center" label="姓名" width="160" prop="name"></el-table-column> <el-table-column
align="center"
label="姓名"
width="160"
prop="name"
></el-table-column>
<el-table-column align="center" label="手机" width="160" prop="mobile"></el-table-column> <el-table-column
align="center"
label="手机"
width="160"
prop="mobile"
></el-table-column>
<!-- <el-table-column width="138px" align="center" label="路线">
<template slot-scope="scope">
<el-tag v-if="scope.row.route_type === 10" type="border-card"
>境内路线</el-tag
>
<el-tag v-if="scope.row.route_type === 20" type="success"
>境外路线</el-tag
>
</template>
</el-table-column> -->
<!-- <el-table-column align="center" label="头像"> <!-- <el-table-column align="center" label="头像">
<template slot-scope="scope"> <template slot-scope="scope">
<el-avatar :size="50" :src="scope.row.avatar"></el-avatar> <el-avatar :size="50" :src="scope.row.avatar"></el-avatar>
</template> </template>
</el-table-column>--> </el-table-column>-->
<el-table-column class-name="status-col" label="状态" width="180"> <el-table-column class-name="status-col" label="状态" width="180">
<template slot-scope="{row}"> <template slot-scope="{ row }">
<el-switch <el-switch
v-model="row.status" v-model="row.status"
@change="setStatus(row)" @change="setStatus(row)"
active-text="启用" active-text="启用"
:active-value="1" :active-value="1"
:inactive-value="0" :inactive-value="0"
inactive-text="禁用"> inactive-text="禁用"
>
</el-switch> </el-switch>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column width="180px" align="center" label="创建时间"> <el-table-column width="180px" align="center" label="创建时间">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ scope.row.create_time | parseTime('{y}-{m}-{d} {h}:{i}') }}</span> <span>{{
scope.row.create_time | parseTime("{y}-{m}-{d} {h}:{i}")
}}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column width="180px" align="center" label="修改时间"> <el-table-column width="180px" align="center" label="修改时间">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ scope.row.update_time | parseTime('{y}-{m}-{d} {h}:{i}') }}</span> <span>{{
scope.row.update_time | parseTime("{y}-{m}-{d} {h}:{i}")
}}</span>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination <pagination
v-show="total>0" v-show="total > 0"
:total="total" :total="total"
:page.sync="listQuery.page" :page.sync="listQuery.page"
:limit.sync="listQuery.limit" :limit.sync="listQuery.limit"
@pagination="getList" @pagination="getList"
/> />
<el-dialog title="修改管理员" :visible.sync="dialogVisible"> <el-dialog title="修改管理员" :visible.sync="dialogVisible">
<el-form label-width="120px" :model="item"> <el-form label-width="120px" :model="item">
<el-form-item label="用户名"> <el-form-item label="用户名">
<el-input v-model="item.username" name="username" placeholder="管理员的用户名"></el-input> <el-input
v-model="item.username"
name="username"
placeholder="管理员的用户名"
></el-input>
</el-form-item> </el-form-item>
<el-form-item label="姓名"> <el-form-item label="姓名">
<el-input v-model="item.name" name="name" placeholder="管理员的姓名"></el-input> <el-input
v-model="item.name"
name="name"
placeholder="管理员的姓名"
></el-input>
</el-form-item> </el-form-item>
<el-form-item label="手机"> <el-form-item label="手机">
<el-input v-model="item.mobile" name="name" placeholder="发送短时的时候会用到这个手机"></el-input> <el-input
v-model="item.mobile"
name="name"
placeholder="发送短时的时候会用到这个手机"
></el-input>
</el-form-item>
<el-form-item label="微信">
<el-input
v-model="item.wechat"
name="wechat"
placeholder="微信号"
></el-input>
</el-form-item>
<el-form-item label="微信图片">
<el-upload
action = ""
list-type="picture-card"
multiple:false
:show-file-list="false"
:http-request="handlesAvatarSuccess"
:on-success="
(response, file, fileList) =>
handleSuccess(response, file, fileList, 1)"
>
<img v-if="item.wechat_pic" :src="item.wechat_pic" style="width: 120px; height: 120px;margin-top: 14px;"/>
<i slot="default" class="el-icon-plus"></i>
</el-upload>
</el-form-item> </el-form-item>
<el-form-item label="密码"> <el-form-item label="密码">
<el-input v-model="item.password" type="password" name="password" placeholder="管理员的新密码"></el-input> <el-input
v-model="item.password"
type="password"
name="password"
placeholder="管理员的新密码"
></el-input>
</el-form-item> </el-form-item>
<el-form-item label="是否分配"> <el-form-item label="是否分配">
<el-switch <el-switch
@ -96,7 +200,7 @@
:active-value="1" :active-value="1"
inactive-text="不分配" inactive-text="不分配"
:inactive-value="0" :inactive-value="0"
> >
</el-switch> </el-switch>
</el-form-item> </el-form-item>
<el-form-item label="是否主播"> <el-form-item label="是否主播">
@ -106,7 +210,7 @@
:active-value="1" :active-value="1"
inactive-text="" inactive-text=""
:inactive-value="0" :inactive-value="0"
> >
</el-switch> </el-switch>
</el-form-item> </el-form-item>
<el-form-item label="是否加盟商"> <el-form-item label="是否加盟商">
@ -116,23 +220,42 @@
:active-value="1" :active-value="1"
inactive-text="" inactive-text=""
:inactive-value="0" :inactive-value="0"
> >
</el-switch> </el-switch>
</el-form-item> </el-form-item>
<el-form-item label="产品ID"> <!-- <el-form-item label="产品ID">
<el-input v-model="item.product_ids" type="textarea" /> <el-input v-model="item.product_ids" type="textarea" />
<span style="color: red;font-size: 11px;">多个用英文逗号隔开例如: 384731,2328</span> <span style="color: red; font-size: 11px"
</el-form-item> >多个用英文逗号隔开例如: 384731,2328</span
<!-- <el-form-item label="路线"> >
<el-checkbox-group v-model="item.routes">
<el-checkbox label="复选框 A">111</el-checkbox>
<el-checkbox label="复选框 B">2222</el-checkbox>
<el-checkbox label="复选框 C">3333</el-checkbox>
<el-checkbox label="禁用" disabled></el-checkbox>
<el-checkbox label="选中且禁用" disabled></el-checkbox>
</el-checkbox-group>
</el-form-item> --> </el-form-item> -->
<el-form-item label="线路">
<el-select
filterable
v-model="item.product_ids"
multiple
placeholder="请选择"
>
<el-option
v-for="item in options"
:key="item.id"
:label="item.product_name"
:value="item.id"
>
</el-option>
</el-select>
</el-form-item>
<div class="addRoutes">
<el-button
class="filter-item"
style="margin-left: 10px"
type="primary"
icon="el-icon-circle-plus"
@click="handleAddRoutes"
>添加线路</el-button
>
</div>
<!-- <el-form-item label="路线"> <!-- <el-form-item label="路线">
<el-select multiple v-model="item.routes" filterable placeholder="请选择"> <el-select multiple v-model="item.routes" filterable placeholder="请选择">
<el-option <el-option
@ -143,12 +266,12 @@
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> --> </el-form-item> -->
<el-form-item label="路线"> <!-- <el-form-item label="路线">
<el-radio-group v-model="item.route_type"> <el-radio-group v-model="item.route_type">
<el-radio :label="10">境内跟团</el-radio> <el-radio :label="10">境内跟团</el-radio>
<el-radio :label="20">境外跟团</el-radio> <el-radio :label="20">境外跟团</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item> -->
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="primary" @click="onSave(item)"> </el-button> <el-button type="primary" @click="onSave(item)"> </el-button>
@ -157,13 +280,13 @@
<el-dialog title="添加排班" :visible.sync="dialogWork"> <el-dialog title="添加排班" :visible.sync="dialogWork">
<el-form label-width="120px" :model="from"> <el-form label-width="120px" :model="from">
<el-form-item label="上班日期"> <el-form-item label="上班日期">
<el-date-picker <el-date-picker
style="margin-right: 10px;" style="margin-right: 10px"
type="dates" type="dates"
v-model="from.dates" v-model="from.dates"
placeholder="选择一个或多个日期"> placeholder="选择一个或多个日期"
>
</el-date-picker> </el-date-picker>
</el-form-item> </el-form-item>
@ -180,127 +303,272 @@
</el-form-item> </el-form-item>
<el-form-item label="渠道"> <el-form-item label="渠道">
<el-checkbox-group v-model="from.oss" > <el-checkbox-group v-model="from.oss">
<el-checkbox v-for="(v,i,k) in oss" :label="i" :key="k">{{v}}</el-checkbox> <el-checkbox v-for="(v, i, k) in oss" :label="i" :key="k">{{
v
}}</el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="primary" @click="onWork()"> </el-button> <el-button type="primary" @click="onWork()"> </el-button>
</div> </div>
</el-dialog> </el-dialog>
<el-dialog title="添加线路" :visible.sync="isAddRouters" width="30%">
<el-form
:rules="addRoutes"
ref="AddRoutersForm"
:model="AddRoutersForm"
label-width="80px"
>
<el-form-item label="平台" prop="os">
<el-select v-model="AddRoutersForm.os" placeholder="请选择平台">
<el-option
v-for="item in platformList"
:label="item.os"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="线路名称" prop="product_name">
<el-input
placeholder="请输入线路名称"
v-model="AddRoutersForm.product_name"
></el-input>
</el-form-item>
<el-form-item label="线路id" prop="third_product_id">
<el-input
placeholder="请输入线路id"
v-model="AddRoutersForm.third_product_id"
></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="isAddRouters = false"> </el-button>
<el-button type="primary" @click="handleAddRouters"> </el-button>
</span>
</el-dialog>
</div> </div>
</template> </template>
<script> <script>
import Pagination from '@/components/Pagination' // Secondary package based on el-pagination import Pagination from "@/components/Pagination"; // Secondary package based on el-pagination
import { getProductsList, addProducts } from "@/api/admin";
import { getToken } from "@/utils/auth";
export default { export default {
name: 'Adminlist', name: "Adminlist",
components: { Pagination }, components: { Pagination },
data() { data() {
return { return {
list: [], list: [],
ids: "",
oss: [], oss: [],
from: {oss:[]}, platformList: [],
isAddRouters: false,
AddRoutersForm: {
third_product_id: "",
product_name: "",
os: "",
},
from: { oss: [] },
total: 0, total: 0,
listLoading: true, listLoading: true,
listQuery: { listQuery: {
page: 1, page: 1,
limit: 20 limit: 20,
}, },
dialogVisible: false, dialogVisible: false,
dialogWork: false, dialogWork: false,
item: { item: {
btn:[] btn: [],
product_ids: [],
}, },
route_type: '', route_type: "",
// options: [{ options: [],
// value: '1', addRoutes: {
// label: '线1' third_product_id: [
// }, { { required: true, message: "请输入线路id", trigger: "blur" },
// value: '2', ],
// label: '线2' os: [{ required: true, message: "请选择平台", trigger: "change" }],
// }, { product_name: [
// value: '3', { required: true, message: "请输入线路名称", trigger: "blur" },
// label: '线3' ],
// }, { },
// value: '4', };
// label: '线4'
// }, {
// value: '5',
// label: '线5'
// }],
}
}, },
created() { created() {
this.getList() this.getList();
}, },
methods: { methods: {
getList() { getList() {
this.$axios.get('/admin/admin/index', { params: this.listQuery }).then(response => { this.$axios
this.list = response.data.data .get("/admin/admin/index", { params: this.listQuery })
this.total = response.data.total .then((response) => {
this.oss = response.ext.oss this.list = response.data.data;
// for(let k in response.ext.oss){ this.total = response.data.total;
// this.oss.push({id:k,val:response.ext.oss[k]}) this.oss = response.ext.oss;
// } // for(let k in response.ext.oss){
console.log(this.oss) // this.oss.push({id:k,val:response.ext.oss[k]})
this.listLoading = false // }
}).catch(err=>{ console.log(this.oss);
}) this.listLoading = false;
})
.catch((err) => {});
},
checkIfUrlContainsImage(url = "") {
const imageExtensions = [
".jpg",
".jpeg",
".png",
".gif",
".bmp",
".svg",
".webp",
];
return imageExtensions.some((extension) =>
url.toLowerCase().endsWith(extension)
);
},
handleAddRoutes() {
this.isAddRouters = true;
},
async handlesAvatarSuccess(file) {
try {
var formdata = new FormData();
formdata.append("file", file.file);
this.upLoading = true;
const _this = this;
const res = await this.$axios.post("/admin/upload/index", formdata, {
headers: {
"Content-type": "multipart/form-data",
"X-Token": getToken(),
},
});
this.item.wechat_pic = `${window.location.protocol}//${window.location.host}${res.data}`;
file.onSuccess(res);
} catch (error) {
console.error('error:', error);
}
},
handleSuccess(res, file, fileList) {
console.log(res, file, fileList);
if (!res.data) return;
this.item.wechat_pic = `${window.location.protocol}//${window.location.host}${res.data}`;
},
handleAddRouters() {
this.$refs["AddRoutersForm"].validate(async (valid) => {
if (valid) {
let add = await addProducts(this.AddRoutersForm);
this.isAddRouters = false;
if (add.msg == "ok") {
this.$message({
message: "线路添加成功",
type: "success",
});
let res = await getProductsList();
this.options = res.data.data;
this.platformList = res.ext.oss;
}
} else {
return false;
}
});
}, },
setStatus(item) { setStatus(item) {
console.log(item) console.log(item);
this.$axios.post('/admin/admin/disabled',item).then(res=>{ this.$axios
this.$notify({ .post("/admin/admin/disabled", item)
title: '成功', .then((res) => {
message: '修改成功', this.$notify({
type: 'success', title: "成功",
duration: 2000 message: "修改成功",
}) type: "success",
}).catch(err=>{ duration: 2000,
this.$notify({ });
title: '成功',
message: '修改失败',
type: 'error',
duration: 2000
}) })
}) .catch((err) => {
this.$notify({
title: "成功",
message: "修改失败",
type: "error",
duration: 2000,
});
});
}, },
handleCreate(item) { async handleCreate(item) {
this.dialogVisible = true; this.dialogVisible = true;
this.item = item let res = await getProductsList();
// console.log(res.data.data);
this.options = res.data.data;
this.platformList = res.ext.oss;
this.item = { ...item };
this.item.product_ids = item.product_ids.split(",").map((item) => {
return Number(item);
});
}, },
onSave(from) { onSave(from) {
this.$axios.post('/admin/admin/save', from).then(response => { this.$axios
this.item = {} .post("/admin/admin/save", {
this.dialogVisible = false ...from,
this.getList() product_ids: from.product_ids.join(),
}).catch(err=>{ })
}) .then((response) => {
this.$message({
message: "成功",
type: "success",
});
this.item = {};
this.dialogVisible = false;
this.getList();
})
.catch((err) => {});
}, },
onWork(item) { onWork(item) {
if(item) { if (item) {
this.item = item this.item = item;
this.from = {oss:[]} this.from = { oss: [] };
this.dialogWork = true this.dialogWork = true;
return return;
} }
this.from.admin_id = this.item.id; this.from.admin_id = this.item.id;
this.$axios.post('/admin/work/saves', this.from).then(response => { this.$axios
this.item = {} .post("/admin/work/saves", this.from)
this.dialogWork = false .then((response) => {
}).catch(err=>{ this.item = {};
this.dialogWork = false;
}) })
.catch((err) => {});
}, },
setTime(val) { setTime(val) {
console.log(val) console.log(val);
} },
} },
} };
</script> </script>
<style lang="scss" scoped>
::v-deep.el-select {
width: 100%;
}
.addRoutes {
display: flex;
justify-content: end;
}
// .el-input__suffix .el-input__suffix-inner {
// //
// .el-input__icon {
// transform: rotateZ(0deg);
// // transition: transform 20s;
// // background: pink;
// &::before {
// display: inline-block;
// transition: transform 0.3s;
// transform: rotateZ(180deg);
// }
// &.is-reverse::before {
// transform: rotateZ(0deg);
// }
// }
// }
</style>

View File

@ -1,226 +1,309 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<div class="filter-container"> <div class="filter-container">
<el-input v-model="listQuery.username" placeholder="用户名" style="width: 200px; margin-right: 10px;" <el-input
class="filter-item" /> v-model="listQuery.mobile"
<el-button class="filter-item" type="primary" icon="el-icon-search" @click="getList"> placeholder="手机号"
style="width: 200px; margin-right: 10px"
class="filter-item"
/>
<el-date-picker
v-model="listQuery.times"
class="filter-item"
type="datetimerange"
range-separator="至"
start-placeholder="开始日期"
:default-time="['00:00:00', '23:59:59']"
end-placeholder="结束日期"
value-format="yyyy-MM-dd HH:mm:ss"
/>
<el-button
class="filter-item"
type="primary"
icon="el-icon-search"
@click="getList"
>
搜索 搜索
</el-button> </el-button>
<el-button class="filter-item" type="primary" icon="el-icon-circle-plus" @click="onAdd"> <el-button
添加主播排班 class="filter-item"
type="primary"
icon="el-icon-circle-plus"
@click="onAdd"
>
添加主播
</el-button> </el-button>
</div> </div>
<el-table v-loading="listLoading" :data="list" border fit highlight-current-row style="width: 100%"> <el-table
v-loading="listLoading"
:data="list"
border
fit
highlight-current-row
style="width: 100%"
>
<el-table-column align="center" fixed label="操作" width="220"> <el-table-column align="center" fixed label="操作" width="220">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="danger" @click="onDel(scope.row)" size="small" icon="el-icon-delete-solid"> <!-- <el-button
type="danger"
@click="onDel(scope.row)"
size="small"
icon="el-icon-delete-solid"
>
删除 删除
</el-button> </el-button> -->
<el-button type="primary" @click="onSave(scope.row)" size="small" icon="el-icon-edit"> <el-button
type="primary"
@click="onAdd(scope.row)"
size="small"
icon="el-icon-edit"
>
修改 修改
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="ID" width="80"> <!-- <el-table-column align="center" label="ID" width="80">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ scope.row.id }}</span> <span>{{ scope.row.id }}</span>
</template> </template>
</el-table-column> </el-table-column> -->
<el-table-column align="center" label="用户名" width="160"> <el-table-column align="center" label="用户名" width="160">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ scope.row.admin.username }}</span> <span>{{ scope.row.username }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="姓名" width="160"> <el-table-column align="center" label="姓名" width="160">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ scope.row.admin.name }}</span> <span>{{ scope.row.name }}</span>
</template> </template>
</el-table-column> </el-table-column>
<!-- <el-table-column align="center" width="100px" label="头像"> <el-table-column align="center" label="手机号" width="160">
<template slot-scope="scope"> <template slot-scope="scope">
<el-avatar :size="50" :src="scope.row.admin.avatar"></el-avatar> <span>{{ scope.row.mobile }}</span>
</template>
</el-table-column>-->
<el-table-column align="center" label="排班时间">
<template slot-scope="scope">
{{ scope.row.start| parseTime('{y}-{m}-{d} {h}:{i}') }} - {{ scope.row.end| parseTime('{y}-{m}-{d} {h}:{i}') }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="岗位" width="160">
<template slot-scope="scope">
<span>{{ scope.row.type == 2 ? "主播" : "中控" }}</span>
</template>
</el-table-column>
<!-- <el-table-column align="center" label="排班时间">
<template slot-scope="scope">
{{ scope.row.start | parseTime("{y}-{m}-{d} {h}:{i}") }} -
{{ scope.row.end | parseTime("{y}-{m}-{d} {h}:{i}") }}
</template>
</el-table-column> -->
<el-table-column align="center" label="平台"> <!-- <el-table-column align="center" label="平台">
<template slot-scope="scope"> <template slot-scope="scope">
<template v-for="i in scope.row.os"> <template v-for="i in scope.row.os">
{{ oss[i] }} {{ oss[i] }}
</template> </template>
</template> </template>
</el-table-column> -->
<el-table-column align="center" label="订单数">
<template slot-scope="scope">
{{ scope.row.orders }}
</template>
</el-table-column>
<el-table-column align="center" label="直播时长(小时)">
<template slot-scope="scope">
{{ scope.row.work_time }}
</template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="订单金额"> <el-table-column align="center" label="订单金额">
<template slot-scope="scope"> <template slot-scope="scope">
{{ scope.row.total/100 }} {{ scope.row.total }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column width="140px" align="center" label="创建时间"> <el-table-column width="140px" align="center" label="创建时间">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ scope.row.create_time | parseTime('{y}-{m}-{d} {h}:{i}') }}</span> <span>{{
scope.row.create_time | parseTime("{y}-{m}-{d} {h}:{i}")
}}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column width="140px" align="center" label="修改时间"> <!-- <el-table-column width="140px" align="center" label="修改时间">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ scope.row.update_time | parseTime('{y}-{m}-{d} {h}:{i}') }}</span> <span>{{
scope.row.update_time | parseTime("{y}-{m}-{d} {h}:{i}")
}}</span>
</template> </template>
</el-table-column> </el-table-column> -->
</el-table> </el-table>
<pagination v-show="total > 0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" /> <pagination
v-show="total > 0"
:total="total"
:page.sync="listQuery.page"
:limit.sync="listQuery.limit"
@pagination="getList"
/>
<!--
<el-dialog title="修改排班" :visible.sync="dialogWork"> <el-dialog title="修改排班" :visible.sync="dialogWork">
<el-form label-width="120px" :model="item"> <el-form label-width="120px" :model="item">
<el-form-item label="上班时间"> <el-form-item label="上班时间">
<el-date-picker <el-date-picker
v-model="item.date" v-model="item.date"
type="datetimerange" type="datetimerange"
range-separator="至" range-separator="至"
start-placeholder="开始日期" start-placeholder="开始日期"
end-placeholder="结束日期"> end-placeholder="结束日期"
>
</el-date-picker> </el-date-picker>
</el-form-item> </el-form-item>
<el-form-item label="渠道"> <el-form-item label="渠道">
<el-checkbox-group v-model="item.oss"> <el-checkbox-group v-model="item.oss">
<el-checkbox v-for="(v,i,k) in oss" :label="i" :key="k">{{v}}</el-checkbox> <el-checkbox v-for="(v, i, k) in oss" :label="i" :key="k">{{
v
}}</el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="primary" @click="onSave()"> </el-button> <el-button type="primary" @click="onSave()"> </el-button>
</div> </div>
</el-dialog> </el-dialog> -->
<el-dialog title="主播排班表" :visible.sync="dialogCreate"> <el-dialog :title="title" :visible.sync="dialogCreate">
<el-form label-width="120px"> <el-form ref="form" :model="form" label-width="80px">
<el-row v-for="an in anchors" :key="an.id" style="margin-bottom: 10px;"> <el-form-item label="用户名" prop="username">
<el-col :span="2">{{ an.name }}</el-col> <el-input placeholder="用户名" v-model="form.username"></el-input>
<el-col :span="14"> </el-form-item>
<el-date-picker <el-form-item label="姓名" prop="name">
v-model="an.times" <el-input placeholder="姓名" v-model="form.name"></el-input>
type="datetimerange" </el-form-item>
range-separator="至" <el-form-item label="手机号" prop="mobile">
start-placeholder="开始日期" <el-input placeholder="请输入手机号" v-model="form.mobile"></el-input>
:default-time="['07:00:00', '23:59:59']" </el-form-item>
style="width:90%" <el-form-item label="岗位" prop="type">
end-placeholder="结束日期"> <el-radio-group v-model="form.type">
</el-date-picker> <el-radio :label="2">主播</el-radio>
</el-col> <el-radio :label="3">中控</el-radio>
<el-col :span="8"> </el-radio-group>
<el-checkbox-group v-model="an.os"> </el-form-item>
<el-checkbox v-for="(v,i,k) in oss" :label="i" :key="k">{{v}}</el-checkbox>
</el-checkbox-group>
</el-col>
</el-row>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="primary" v-loading="loading" @click="onSaves()"> </el-button> <el-button type="primary" v-loading="loading" @click="onSaves(form)"
> </el-button
>
</div> </div>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script> <script>
import Pagination from '@/components/Pagination' // Secondary package based on el-pagination import Pagination from "@/components/Pagination"; // Secondary package based on el-pagination
export default { export default {
name: 'Works', name: "Works",
components: { Pagination }, components: { Pagination },
data() { data() {
return { return {
oss: {}, oss: {},
list: null, list: null,
title: "",
total: 0, total: 0,
listLoading: true, listLoading: true,
loading: false, loading: false,
listQuery: { listQuery: {
page: 1, page: 1,
limit: 20 limit: 20,
},
form: {
username: "",
name: "",
mobile: "",
type: 2,
}, },
dialogWork: false, dialogWork: false,
dialogCreate: false, dialogCreate: false,
item: {}, item: {},
anchors: [] anchors: [],
} };
}, },
created() { created() {
this.getList() this.getList();
}, },
methods: { methods: {
getList() { getList() {
this.$axios.get('/admin/work/index', { params: this.listQuery }).then(response => { this.$axios
this.list = response.data.data .get("/admin/liveroom/zhuboStatistics", { params: this.listQuery })
this.total = response.data.total .then((response) => {
this.oss = response.ext.oss this.list = response.data.data;
this.listLoading = false this.total = response.data.total;
}).catch(err => { this.listLoading = false;
}) })
.catch((err) => {});
}, },
onDel(item) { onDel(item) {
this.$axios.post('/admin/work/del', {id: item.id}).then(res => { this.$axios
this.dialogVisible = false .post("/admin/work/del", { id: item.id })
this.getList() .then((res) => {
}).catch(err => { this.dialogVisible = false;
this.getList();
}) })
.catch((err) => {});
}, },
onSave(from) { // onSave(from) {
if(from) { // if (from) {
this.$set(from,'date',[new Date(from.start), new Date(from.end)]); // this.$set(from, "date", [new Date(from.start), new Date(from.end)]);
this.item = from; // this.item = from;
this.item.os = from.os+''; // this.item.os = from.os + "";
this.dialogWork = true; // this.dialogWork = true;
return // return;
} // }
this.$axios.post('/admin/work/save', this.item).then(response => { // this.$axios
this.dialogWork = false // .post("/admin/work/save", this.item)
this.getList() // .then((response) => {
}).catch(err => { // this.dialogWork = false;
}) // this.getList();
}, // })
onAdd() { // .catch((err) => {});
this.$axios.get('/admin/work/anchor').then((res) => { // },
this.anchors = res.data || [] onAdd(item) {
this.dialogCreate = true // this.anchors = res.data || [];
}).catch(err=>{ this.dialogCreate = true;
this.$nextTick((res) => {
}) this.$refs["form"].resetFields();
this.title = item.id ? "编辑" : "添加";
this.form = item.id
? { ...item }
: {
username: "",
name: "",
mobile: "",
type: 2,
};
});
}, },
onSaves() { onSaves() {
if(this.loading) return this.$axios
this.loading = true .post("/admin/admin/save", this.form)
this.$axios.post('/admin/work/save2', this.anchors).then(res => { .then((response) => {
this.dialogCreate = false this.$message({
this.loading = false message: "成功",
}).catch(err=> { type: "success",
this.loading = false });
}) this.dialogCreate = false;
} this.getList();
} })
} .catch((err) => {});
},
},
};
</script> </script>

View File

@ -27,14 +27,14 @@
<el-button class="filter-item" type="primary" icon="el-icon-search" @click="getList"> <el-button class="filter-item" type="primary" icon="el-icon-search" @click="getList">
搜索 搜索
</el-button> </el-button>
<!-- <el-button <el-button
class="filter-item" class="filter-item"
type="primary" type="primary"
icon="el-icon-search" icon="el-icon-search"
@click="getList(1)" @click="getList(1)"
> >
导出 导出
</el-button> --> </el-button>
</div> </div>
<el-table v-loading="listLoading" :data="list" border fit highlight-current-row style="width: 100%"> <el-table v-loading="listLoading" :data="list" border fit highlight-current-row style="width: 100%">
@ -77,6 +77,17 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="当月核销率(按订单)" width="160">
<template slot-scope="scope">
{{ scope.row.month_write_rate }}%
</template>
</el-table-column>
<el-table-column align="center" width="180" label="当月核销率(按销售额)">
<template slot-scope="scope">
{{ scope.row.month_write_rate_price }}%
</template>
</el-table-column>
</el-table> </el-table>
</div> </div>

View File

@ -40,11 +40,11 @@
<span>{{ scope.row.last_work_time | parseTime('{y}-{m}-{d} {h}:{i}') }}</span> <span>{{ scope.row.last_work_time | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column width="138px" align="center" label="线"> <!-- <el-table-column width="138px" align="center" label="线">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ scope.row.last_work_time | parseTime('{y}-{m}-{d} {h}:{i}') }}</span> <el-tag type="border-card">{{ scope.row.routes }}</el-tag>
</template> </template>
</el-table-column> </el-table-column> -->
</el-table> </el-table>
</div> </div>
</template> </template>

View File

@ -578,7 +578,7 @@ export default {
flowObj: "", flowObj: "",
os: null, // 12 3 os: null, // 12 3
}, },
os_arr: { 1: "美团", 2: "快手", 3: "抖音" }, os_arr: { 1: "美团", 2: "快手", 3: "抖音(甄选)", 4:"全平台", 5:"抖音(新国旅)" },
adminList: [], adminList: [],
form: {}, form: {},
rules: { rules: {
@ -628,12 +628,14 @@ export default {
this.$axios this.$axios
.get("/admin/order/index", { params: this.listQuery }) .get("/admin/order/index", { params: this.listQuery })
.then((response) => { .then((response) => {
console.log(response);
this.list = response.data.data; this.list = response.data.data;
this.total = response.data.total; this.total = response.data.total;
(this.timetype_arr = response.ext.timetype), (this.timetype_arr = response.ext.timetype),
(this.oss = response.ext.oss); (this.oss = response.ext.oss);
this.listLoading = false; this.listLoading = false;
}); })
}, },
objectToQuery(obj) { objectToQuery(obj) {
return Object.keys(obj) return Object.keys(obj)

View File

@ -0,0 +1,116 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.sn" placeholder="订单号" style="width: 300px;" class="filter-item" />
<el-button class="filter-item" type="primary" icon="el-icon-search" @click="getList">
搜索
</el-button>
</div>
<el-table v-loading="listLoading" :data="list" border fit highlight-current-row style="width: 100%">
<el-table-column align="center" fixed width="220" label="操作">
<template slot-scope="scope">
<el-button v-if="scope.row.self ==0 && scope.row.status == 0" type="primary" size="small" icon="el-icon-check" @click="onPass(scope.row)">
确认
</el-button>
</template>
</el-table-column>
<el-table-column align="center" label="订单号" width="80" prop="orderInfo.sn" />
<el-table-column align="center" label="出游日期" width="80" prop="travel_date" />
<el-table-column align="center" label="出游人数" width="80" prop="num" />
<el-table-column align="center" label="出行人名称" width="220" prop="name" />
<el-table-column align="center" label="联系电话" prop="mobile" />
<el-table-column align="center" label="券码图片" width="120">
<template slot-scope="scope">
<el-image
style="width: 100px; height: 100px"
:src="scope.row.code_pic"
:preview-src-list="[scope.row.code_pic]">
</el-image>
</template>
</el-table-column>
<el-table-column align="center" label="备注" prop="note" />
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="listQuery.page"
:limit.sync="listQuery.limit"
@pagination="getList"
/>
</div>
</template>
<script>
// import Pagination from '@/Wangeditor/Pagination'
import Pagination from '@/components/PaginationFixed'
export default {
name: 'Orderlist',
components: { Pagination },
filters: {
},
data() {
return {
list: [],
total: 0,
listLoading: true,
listQuery: {
page: 1,
limit: 10
},
oss: {},
item: {},
dialogVisible: false
}
},
created() {
this.getList()
},
methods: {
getList() {
this.$axios.get('/admin/orderbooks/list', { params: this.listQuery }).then(response => {
this.list = response.data.data
this.total = response.data.total
this.oss = response.ext
this.listLoading = false
})
},
onBack() {
this.$axios.post('/admin/order/back', this.item).then(res => {
this.dialogVisible = false
this.item = {}
this.getList()
}).catch(err => {
})
},
onPass(item) {
this.$axios.post('/admin/order/backpass', { id: item.id }).then(res => {
this.dialogVisible = false
this.item = {}
this.getList()
}).catch(err => {
})
}
}
}
</script>
<style scoped>
.app-container {
position: relative;
padding-bottom: 60px; /* 分页条的高度 */
}
.filter-container,
.el-table {
padding-bottom: 52px; /* 分页条的高度,以避免内容重叠 */
}
</style>

View File

@ -39,6 +39,8 @@
v-model="listQuery.os_status" v-model="listQuery.os_status"
placeholder="平台状态" placeholder="平台状态"
:options="oss" :options="oss"
clearable
:props="{ checkStrictly: true }"
class="filter-item" class="filter-item"
@change="handleChange" @change="handleChange"
/> />
@ -87,13 +89,14 @@
<el-select <el-select
v-model="listQuery.appointment_status" v-model="listQuery.appointment_status"
filterable filterable
clearable
placeholder="预约状态" placeholder="预约状态"
class="filter-item" class="filter-item"
style="width: 120px" style="width: 120px"
> >
<el-option key="" label="请选择" value="" />
<el-option key="1" label="已预约" value="1" />
<el-option key="0" label="未预约" value="0" /> <el-option key="0" label="未预约" value="0" />
<el-option key="1" label="已预约(未处理)" value="1" />
<el-option key="2" label="已预约(已处理)" value="2" />
</el-select> </el-select>
<el-button <el-button
@ -122,6 +125,15 @@
> >
核单 核单
</el-button> </el-button>
<el-button
type="primary"
:disabled="multipleSelection.length == 0"
class="filter-item"
icon="el-icon-refresh"
@click="onCirculationAll()"
>
批量流转
</el-button>
</div> </div>
<el-table <el-table
@ -132,7 +144,9 @@
highlight-current-row highlight-current-row
style="width: 100%" style="width: 100%"
:height="tableMaxHeight" :height="tableMaxHeight"
@selection-change="handleSelectionChange"
> >
<el-table-column type="selection" width="55"> </el-table-column>
<el-table-column align="center" fixed width="200" label="操作"> <el-table-column align="center" fixed width="200" label="操作">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button-group> <el-button-group>
@ -167,6 +181,15 @@
> >
同步 同步
</el-button> </el-button>
<el-button
v-if="scope.row.appointment_status == 1"
size="small"
icon="el-icon-edit"
@click="onOneClickYyHandle(scope.row)"
>
预约处理
</el-button>
</el-button-group> </el-button-group>
</template> </template>
</el-table-column> </el-table-column>
@ -181,7 +204,11 @@
<el-table-column align="center" fixed label="电话" width="140"> <el-table-column align="center" fixed label="电话" width="140">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ scope.row.mobile }}</span> <span>{{ scope.row.mobile }}</span>
<span style="display:block;font-size: 12px;">{{ scope.row.mobileInfo.area }}-{{ scope.row.mobileInfo.originalIsp }}</span> <span style="display: block; font-size: 12px"
>{{ scope.row.mobileInfo.area }}-{{
scope.row.mobileInfo.originalIsp
}}</span
>
</template> </template>
</el-table-column> </el-table-column>
@ -234,7 +261,13 @@
}" }"
type="primary" type="primary"
> >
{{ scope.row.appointment_status == 1 ? '已预约' : '未预约' }} {{
scope.row.appointment_status == 1
? "已预约(未处理)"
: scope.row.appointment_status == 2
? "已预约(已处理)"
: "未预约"
}}
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
@ -424,7 +457,7 @@
> >
</el-form-item> </el-form-item>
<el-form-item label="出游日期"> <el-form-item required pros="travel_date" label="出游日期">
<el-date-picker <el-date-picker
v-model="item.travel_date" v-model="item.travel_date"
type="date" type="date"
@ -440,7 +473,12 @@
/> />
</el-form-item> </el-form-item>
<el-form-item label="下次跟进时间" v-if="item.status !== 2"> <el-form-item
required
pros="next_follow"
label="下次跟进时间"
v-if="item.status !== 2"
>
<el-date-picker <el-date-picker
v-model="next_follow" v-model="next_follow"
type="datetime" type="datetime"
@ -470,7 +508,7 @@
</el-select> </el-select>
</el-form-item>--> </el-form-item>-->
<el-form-item label="跟进说明"> <el-form-item required pros="desc" label="跟进说明">
<el-input v-model="item.desc" type="textarea" /> <el-input v-model="item.desc" type="textarea" />
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -538,10 +576,10 @@
<el-dialog title="申请转出订单" :visible.sync="applyVisible"> <el-dialog title="申请转出订单" :visible.sync="applyVisible">
<el-form label-width="160px" :model="item3" :rules="rules" ref="ruleForm"> <el-form label-width="160px" :model="item3" :rules="rules" ref="ruleForm">
<el-form-item label="标题:"> <el-form-item label="标题:" v-if="!isAll">
<el-input v-model="item3.product_name" disabled /> <el-input v-model="item3.product_name" disabled />
</el-form-item> </el-form-item>
<el-form-item label="订单号:"> <el-form-item label="订单号:" v-if="!isAll">
<el-input v-model="item3.sn" disabled /> <el-input v-model="item3.sn" disabled />
</el-form-item> </el-form-item>
<el-form-item label="流转对象:" style="width: 600px" prop="flowObj"> <el-form-item label="流转对象:" style="width: 600px" prop="flowObj">
@ -590,7 +628,7 @@
<script> <script>
// import Pagination from '@/Wangeditor/Pagination' // import Pagination from '@/Wangeditor/Pagination'
import Pagination from "@/components/PaginationFixed"; import Pagination from "@/components/PaginationFixed";
import { orderBack } from "@/api/order"; import { orderBack, orderbackBatch } from "@/api/order";
export default { export default {
name: "Orderlist", name: "Orderlist",
components: { Pagination }, components: { Pagination },
@ -642,8 +680,9 @@ export default {
admin: null, admin: null,
zhubo: null, zhubo: null,
os_status: [], os_status: [],
appointment_status: "",
}, },
item: { next_follow: "", personnel: {} }, item: { next_follow: "", personnel: { adult: "" } },
follow: [], follow: [],
dialogVisible: false, dialogVisible: false,
@ -657,9 +696,12 @@ export default {
flowObj: "", flowObj: "",
os: null, // 12 3 os: null, // 12 3
}, },
os_arr: { 1: "美团", 2: "快手", 3: "抖音" }, os_arr: { 1: "美团", 2: "快手", 3: "抖音(甄选)", 5: "抖音(新国旅)" },
multipleSelection: [],
sn: [],
adminList: [], adminList: [],
form: {}, form: {},
isAll: false,
rules: { rules: {
flowObj: [ flowObj: [
{ required: true, message: "请选择流转对象", trigger: "change" }, { required: true, message: "请选择流转对象", trigger: "change" },
@ -673,21 +715,47 @@ export default {
if (this.$route.query.start && this.$route.query.end) { if (this.$route.query.start && this.$route.query.end) {
this.listQuery.times = [this.$route.query.start, this.$route.query.end]; this.listQuery.times = [this.$route.query.start, this.$route.query.end];
} }
// console.log(typeof this.$route.params.id);
this.setQuery("status"); this.setQuery("status");
this.setQuery("os_status"); this.setQuery("os_status");
this.setQuery("times"); this.setQuery("times");
await this.getList(); this.setQuery("appointment_status");
await this.setOneClickRepair(); // await this.getList();
await this.getList(); // if (!this.$route.query.id) {
// await this.setOneClickRepair();
// await this.getList();
// }
this.getShortcutContent(); this.getShortcutContent();
this.getAdminList(); this.getAdminList();
}, },
computed: { mounted() {
tableMaxHeight() { console.log(this.$route.query);
return window.innerHeight - 320 + 'px'; this.setMode();
if (this.$route.query.id) {
this.onInfo({ id: this.$route.query.id });
} }
}, },
computed: {
tableMaxHeight() {
return window.innerHeight - 320 + "px";
},
},
watch: {
$route(to, from) {
this.onInfo({ id: this.$route.query.id });
},
},
methods: { methods: {
handleSelectionChange(val) {
// console.log(val);
this.multipleSelection = val;
const data = [];
this.multipleSelection.map((item) => {
data.push(item.sn);
});
this.sn = data;
},
setQuery(key) { setQuery(key) {
if (this.$route.query.hasOwnProperty(key)) { if (this.$route.query.hasOwnProperty(key)) {
this.listQuery[key] = this.$route.query[key]; this.listQuery[key] = this.$route.query[key];
@ -746,7 +814,7 @@ export default {
this.value = null; this.value = null;
this.next_follow = null; this.next_follow = null;
this.$set(item, "next_follow", null); this.$set(item, "next_follow", null);
this.item = item; // this.item = item;
this.active = "follow"; this.active = "follow";
this.$axios this.$axios
.get("/admin/order/info", { params: { id: item.id } }) .get("/admin/order/info", { params: { id: item.id } })
@ -759,10 +827,10 @@ export default {
resetForm(formName) { resetForm(formName) {
this.$refs[formName].resetFields(); this.$refs[formName].resetFields();
}, },
getAdminList(typeDesc = '') { getAdminList(typeDesc = "") {
this.$axios this.$axios
.get("/admin/admin/index", { .get("/admin/admin/index", {
params: { limit: 100, status: 1, is_order: 1, type_desc:typeDesc }, params: { limit: 100, status: 1, is_order: 1, type_desc: typeDesc },
}) })
.then((response) => { .then((response) => {
this.adminList = response.data.data; this.adminList = response.data.data;
@ -773,6 +841,7 @@ export default {
onCirculation(item) { onCirculation(item) {
this.getAdminList(item.category_desc); this.getAdminList(item.category_desc);
this.applyVisible = true; this.applyVisible = true;
this.isAll = false;
this.item3 = { ...item, os: Number(item.os) }; this.item3 = { ...item, os: Number(item.os) };
console.log(this.item3); console.log(this.item3);
if (this.item3.backs && this.item3.backs.admin_id) { if (this.item3.backs && this.item3.backs.admin_id) {
@ -781,18 +850,52 @@ export default {
this.resetForm("ruleForm"); this.resetForm("ruleForm");
} }
}, },
//
onCirculationAll() {
this.applyVisible = true;
this.isAll = true;
},
// //
onCirculationSave(to_admin_id) { onCirculationSave(to_admin_id) {
this.$refs.ruleForm.validate((valid) => { this.$refs.ruleForm.validate(async (valid) => {
if (valid) { if (valid) {
orderBack({ let res;
sn: this.item3.sn, if (this.isAll) {
os: this.item3.os, this.$confirm("是否批量流转订单", "提示", {
to_admin_id: to_admin_id, confirmButtonText: "确定",
}).then((res) => { cancelButtonText: "取消",
this.applyVisible = false; type: "warning",
this.getList(); }).then(async () => {
}); res = await orderbackBatch({
sn: this.sn,
to_admin_id: to_admin_id,
});
if (res.data) {
this.$message({
message: "批量流转订单成功",
type: "success",
});
this.applyVisible = false;
this.isAll = false;
this.getList();
}
});
} else {
res = await orderBack({
sn: this.item3.sn,
os: this.item3.os,
to_admin_id: to_admin_id,
});
if (res.data) {
this.$message({
message: "流转订单成功",
type: "success",
});
this.applyVisible = false;
this.isAll = false;
this.getList();
}
}
} else { } else {
return false; return false;
} }
@ -806,6 +909,7 @@ export default {
.post("/admin/order/backcancel", { id: this.item3.id }) .post("/admin/order/backcancel", { id: this.item3.id })
.then((res) => { .then((res) => {
this.applyVisible = false; this.applyVisible = false;
this.isAll = false;
this.getList(); this.getList();
}) })
.catch((err) => { .catch((err) => {
@ -827,8 +931,6 @@ export default {
.catch((err) => {}); .catch((err) => {});
}, },
onSave(item) { onSave(item) {
console.log(this.next_follow);
this.$axios this.$axios
.post("/admin/order/save", { .post("/admin/order/save", {
id: item.id, id: item.id,
@ -843,7 +945,10 @@ export default {
}) })
.then((res) => { .then((res) => {
this.dialogVisible = false; this.dialogVisible = false;
this.item = { next_follow: "", personnel: {} }; this.item = { next_follow: "", personnel: { adult: "" } };
this.$router.push({
path: "/order/index",
});
}) })
.catch((err) => {}); .catch((err) => {});
}, },
@ -890,7 +995,6 @@ export default {
this.$axios this.$axios
.post("/admin/order/oneClickRepair", { id: item.id }) .post("/admin/order/oneClickRepair", { id: item.id })
.then((res) => { .then((res) => {
this.dialogVisible = false;
this.$notify({ this.$notify({
title: "成功", title: "成功",
message: "同步完成", message: "同步完成",
@ -898,6 +1002,25 @@ export default {
}); });
// this.getList(); // this.getList();
}) })
.catch((err) => {
this.$notify.error({
title: "同步失败",
message: err,
});
});
},
onOneClickYyHandle(item) {
this.$axios
.post("/admin/order/changeAppointmentStatus", { id: item.id })
.then((res) => {
this.dialogVisible = false;
this.$notify({
title: "成功",
message: "已处理",
type: "success",
});
// this.getList();
})
.catch((err) => { .catch((err) => {
this.$notify.error({ this.$notify.error({
title: "错误", title: "错误",

View File

@ -25,14 +25,14 @@
搜索 搜索
</el-button> </el-button>
<!-- <el-button <el-button
class="filter-item" class="filter-item"
type="primary" type="primary"
icon="el-icon-search" icon="el-icon-search"
@click="getList(1)" @click="getList(1)"
> >
导出 导出
</el-button> --> </el-button>
</div> </div>
@ -45,6 +45,7 @@
<el-tag type="warning" v-if="scope.row.os === 1">美团</el-tag> <el-tag type="warning" v-if="scope.row.os === 1">美团</el-tag>
<el-tag type="success" v-if="scope.row.os === 2">快手</el-tag> <el-tag type="success" v-if="scope.row.os === 2">快手</el-tag>
<el-tag type="primary" v-if="scope.row.os === 3">抖音</el-tag> <el-tag type="primary" v-if="scope.row.os === 3">抖音</el-tag>
<el-tag type="primary" v-if="scope.row.os === 5">抖音(新国旅)</el-tag>
</template> </template>
</el-table-column> </el-table-column>
@ -144,6 +145,9 @@ export default {
}, { }, {
value: '3', value: '3',
label: '抖音' label: '抖音'
}, {
value: '5',
label: '抖音(新国旅)'
}], }],
form: {} form: {}
} }

View File

@ -0,0 +1,992 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input
v-model="listQuery.sn"
placeholder="订单号"
style="width: 200px"
class="filter-item"
/>
<el-input
v-model="listQuery.product_name"
placeholder="标题"
style="width: 200px"
class="filter-item"
/>
<el-input
v-model="listQuery.mobile"
placeholder="手机号"
style="width: 200px"
class="filter-item"
/>
<el-input
v-model="listQuery.zhubo"
placeholder="主播"
style="width: 100px"
class="filter-item"
/>
<el-input
v-model="listQuery.admin"
placeholder="客服"
style="width: 100px"
class="filter-item"
/>
<!-- <el-cascader
v-model="listQuery.os_status"
placeholder="平台状态"
:options="oss"
clearable
:props="{ checkStrictly: true }"
class="filter-item"
@change="handleChange"
/> -->
<el-select
v-model="listQuery.status"
filterable
placeholder="跟进状态"
class="filter-item"
style="width: 120px"
>
<el-option key="" label="请选择" value="" />
<el-option
v-for="(v, k) in status_arr"
:key="k"
:label="v"
:value="k"
/>
</el-select>
<el-select
v-model="listQuery.timetype"
filterable
placeholder="时间"
class="filter-item"
style="width: 120px"
>
<el-option key="" label="请选择" value="" />
<el-option
v-for="(v, k) in timetype_arr"
:key="k"
:label="v"
:value="k"
/>
</el-select>
<el-date-picker
v-model="listQuery.times"
class="filter-item"
type="datetimerange"
range-separator="至"
start-placeholder="开始日期"
:default-time="['00:00:00', '23:59:59']"
end-placeholder="结束日期"
/>
<el-select
v-model="listQuery.appointment_status"
filterable
clearable
placeholder="预约状态"
class="filter-item"
style="width: 120px"
>
<el-option key="0" label="未预约" value="0" />
<el-option key="1" label="已预约(未处理)" value="1" />
<el-option key="2" label="已预约(已处理)" value="2" />
</el-select>
<el-button
class="filter-item"
type="primary"
icon="el-icon-search"
@click="getList"
>
搜索
</el-button>
<el-button
class="filter-item"
type="primary"
icon="el-icon-search"
@click="getList(1)"
>
导出
</el-button>
<!-- <el-button
class="filter-item"
type="primary"
icon="el-icon-search"
@click="dialog2Visible = true"
>
核单
</el-button> -->
<!-- <el-button
type="primary"
:disabled="multipleSelection.length == 0"
class="filter-item"
icon="el-icon-refresh"
@click="onCirculationAll()"
>
批量流转
</el-button> -->
</div>
<el-table
v-loading="listLoading"
:data="list"
border
fit
highlight-current-row
style="width: 100%"
:height="tableMaxHeight"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55"> </el-table-column>
<el-table-column align="center" fixed label="电话" width="140">
<template slot-scope="scope">
<span>{{ scope.row.mobile }}</span>
<span style="display: block; font-size: 12px"
>{{ scope.row.mobileInfo.area }}-{{
scope.row.mobileInfo.originalIsp
}}</span
>
</template>
</el-table-column>
<el-table-column
align="center"
fixed
label="平台"
width="80"
prop="os_name"
/>
<el-table-column align="center" fixed label="直播" width="60">
<template slot-scope="scope">
<el-tag v-if="scope.row.is_zhibo"></el-tag>
<el-tag v-else type="info"></el-tag>
</template>
</el-table-column>
<el-table-column
align="center"
fixed
label="客服"
width="80"
prop="admin.username"
/>
<el-table-column align="center" label="订单号" width="180" prop="sn" />
<el-table-column width="138px" align="center" label="下单时间">
<template slot-scope="scope">
<span>{{
scope.row.create_at | parseTime("{y}-{m}-{d} {h}:{i}")
}}</span>
</template>
</el-table-column>
<el-table-column width="160px" align="center" label="派单时间">
<template slot-scope="scope">
<span>{{
scope.row.give_time | parseTime("{y}-{m}-{d} {h}:{i}")
}}</span>
</template>
</el-table-column>
<el-table-column align="center" label="预约状态(抖音)" width="80">
<template slot-scope="scope">
<div
style="padding: 1px 5px; border-radius: 3px"
:style="{
color: order_status[scope.row.appointment_status],
border: `1px solid ${order_status[scope.row.appointment_status]}`,
}"
type="primary"
>
{{
scope.row.appointment_status == 1
? "已预约(未处理)"
: scope.row.appointment_status == 2
? "已预约(已处理)"
: "未预约"
}}
</div>
</template>
</el-table-column>
<el-table-column align="center" label="状态" width="80">
<template slot-scope="scope">
<div
style="padding: 1px 5px; border-radius: 3px"
:style="{
color: order_status[scope.row.order_status],
border: `1px solid ${order_status[scope.row.order_status]}`,
}"
type="primary"
>
{{ scope.row.order_status_name }}
</div>
</template>
</el-table-column>
<el-table-column align="center" label="跟进状态" width="90">
<template slot-scope="scope">
<div
style="padding: 1px 5px; border-radius: 3px"
:style="{
color: follow_status[scope.row.status],
border: `1px solid ${follow_status[scope.row.status]}`,
}"
type="primary"
>
{{ scope.row.status_name }}
</div>
</template>
</el-table-column>
<el-table-column
align="center"
width="500px"
label="标题"
prop="product_name"
/>
<el-table-column
width="500px"
align="center"
label="跟进备注"
prop="remark"
/>
<el-table-column
align="center"
label="联系人"
width="120"
prop="contact"
/>
<!-- <el-table-column align="center" label="微信" width="80">
<template slot-scope="scope">
<i v-if="scope.row.is_wechat>0" class="el-icon-circle-check"></i>
</template>
</el-table-column> -->
<el-table-column width="138px" align="center" label="出行时间">
<template slot-scope="scope">
<span>{{ scope.row.travel_date | parseTime("{y}-{m}-{d}") }}</span>
</template>
</el-table-column>
<el-table-column width="138px" align="center" label="最后跟进时间">
<template slot-scope="scope">
<span>{{
scope.row.last_follow | parseTime("{y}-{m}-{d} {h}:{i}")
}}</span>
</template>
</el-table-column>
<el-table-column align="center" label="核单" width="80">
<template slot-scope="scope">
<i v-if="scope.row.is_check == 1" class="el-icon-check" />
<i v-if="scope.row.is_check == 2" class="el-icon-close" />
</template>
</el-table-column>
<el-table-column
align="center"
width="138px"
label="分类"
prop="category_desc"
/>
<el-table-column align="center" label="总金额" width="120">
<template slot-scope="scope">
<span>{{ scope.row.total_price / 100 }}</span>
</template>
</el-table-column>
<el-table-column
align="center"
width="80px"
label="人数"
prop="quantity"
/>
<el-table-column
align="center"
label="主播"
width="80"
prop="anchor.username"
/>
<el-table-column width="138px" align="center" label="修改时间">
<template slot-scope="scope">
<span>{{
scope.row.update_time | parseTime("{y}-{m}-{d} {h}:{i}")
}}</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="listQuery.page"
:limit.sync="listQuery.limit"
@pagination="setMode"
/>
<el-dialog title="订单跟进" :visible.sync="dialogVisible">
<el-form label-width="130px" :model="item">
<el-form-item label="产品名称">
{{ item.product_name }}
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="产品状态">
{{ item.order_status_name }}
</el-form-item>
<el-form-item label="数量">
{{ item.quantity }}
</el-form-item>
<el-form-item label="联系人">
{{ item.contact }}
</el-form-item>
<el-form-item label="手机">
{{ item.mobile }}
</el-form-item>
<el-form-item label="下单时间">
{{ item.create_at | parseTime("{y}-{m}-{d} {h}:{i}") }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="人员">
<el-row>
<el-col :span="3">大人</el-col>
<el-col :span="5"
><el-input
v-model="item.personnel.adult"
name="adult"
placeholder="大人"
/></el-col>
<el-col :span="3">老人</el-col>
<el-col :span="5"
><el-input
v-model="item.personnel.old"
name="old"
placeholder="老人"
/></el-col>
<el-col :span="3">小孩</el-col>
<el-col :span="5"
><el-input
v-model="item.personnel.child"
name="child"
placeholder="小孩"
/></el-col>
</el-row>
</el-form-item>
<el-form-item v-if="item.status !== 1" label="核销码">
<el-input
v-model="item.check_sn"
name="check_sn"
placeholder="请输入平台核销码"
/>
</el-form-item>
<el-form-item label="加微信" v-if="item.status !== 2">
<el-checkbox
v-model="item.is_wechat"
:true-label="1"
:false-label="0"
>已加微信</el-checkbox
>
</el-form-item>
<el-form-item required pros="travel_date" label="出游日期">
<el-date-picker
v-model="item.travel_date"
type="date"
placeholder="选择日期时间"
/>
</el-form-item>
<el-form-item v-if="item.status !== 1" label="返回日期">
<el-date-picker
v-model="item.travel_end"
type="date"
placeholder="选择日期时间"
/>
</el-form-item>
<el-form-item
required
pros="next_follow"
label="下次跟进时间"
v-if="item.status !== 2"
>
<el-date-picker
v-model="next_follow"
type="datetime"
placeholder="选择日期时间"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="跟进状态">
<template v-for="(v, k) in status_arr">
<el-radio v-if="k > 0" v-model="item.status" :label="k" border>{{
v
}}</el-radio>
</template>
</el-form-item>
<!-- <el-form-item label="快捷跟进" style="width: 600px;">
<el-select v-model="value" placeholder="请选择" @change="onChange">
<el-form-item style="display: inline-flex;text-align: left;width: 770px;">
<el-option
v-for="item in options"
:key="item.value"
style="width: 250px;display: inline-flex;word-break: break-all;"
:label="item.label"
:value="item.label"
/>
</el-form-item>
</el-select>
</el-form-item>-->
<el-form-item required pros="desc" label="跟进说明">
<el-input v-model="item.desc" type="textarea" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="onSave(item)"> </el-button>
</div>
<el-tabs v-model="active" type="border-card">
<el-tab-pane name="follow" label="跟进记录">
<el-table :data="item.follow" style="width: 100%">
<el-table-column label="日期" width="138">
<template slot-scope="scope">
<span>{{
scope.row.create_time | parseTime("{y}-{m}-{d} {h}:{i}")
}}</span>
</template>
</el-table-column>
<el-table-column label="跟进人" width="110" prop="name" />
<el-table-column label="状态" width="80">
<template slot-scope="scope">
<span>{{ status_arr[scope.row.status] }}</span>
</template>
</el-table-column>
<el-table-column prop="desc" label="跟进说明" />
</el-table>
</el-tab-pane>
<el-tab-pane name="finance" label="财务记录">
<el-table :data="item.finance" style="width: 100%">
<el-table-column label="日期">
<template slot-scope="scope">
<span>{{
scope.row.create_time | parseTime("{y}-{m}-{d} {h}:{i}")
}}</span>
</template>
</el-table-column>
<el-table-column label="类型" width="110">
<template slot-scope="scope">
<span>{{ type_arr[scope.row.type] }}</span>
</template>
</el-table-column>
<el-table-column label="状态" width="120">
<template slot-scope="scope">
<span>{{ scope.row.total / 100 }}</span>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</el-dialog>
<el-dialog title="纯核销" :visible.sync="dialog2Visible">
<el-form label-width="160px" :model="form">
<el-form-item label="平台">
<el-radio v-model="form.os" label="1">美团</el-radio>
</el-form-item>
<el-form-item label="核销码">
<el-input v-model="form.check_sn" placeholder="请输入平台核销码" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="onPass(form)"> </el-button>
</div>
</el-dialog>
<el-dialog title="申请转出订单" :visible.sync="applyVisible">
<el-form label-width="160px" :model="item3" :rules="rules" ref="ruleForm">
<el-form-item label="标题:" v-if="!isAll">
<el-input v-model="item3.product_name" disabled />
</el-form-item>
<el-form-item label="订单号:" v-if="!isAll">
<el-input v-model="item3.sn" disabled />
</el-form-item>
<el-form-item label="流转对象:" style="width: 600px" prop="flowObj">
<el-select
v-model="item3.flowObj"
placeholder="请选择"
@change="onChange2"
>
<el-form-item
style="display: inline-flex; text-align: left; width: 770px"
>
<el-option
v-for="item in adminList"
:key="item.value"
style="
width: 250px;
display: inline-flex;
word-break: break-all;
"
:label="item.username"
:value="item.id"
/>
</el-form-item>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<!-- scope.row.backs&&scope.row.backs.status==2? -->
<el-button
v-if="item3.backs && item3.backs.status == 0"
type="primary"
@click="onCancel(item3.flowObj)"
> </el-button
>
<el-button
v-else
type="primary"
@click="onCirculationSave(item3.flowObj)"
> </el-button
>
</div>
</el-dialog>
</div>
</template>
<script>
// import Pagination from '@/Wangeditor/Pagination'
import Pagination from "@/components/PaginationFixed";
import { orderBack, orderbackBatch } from "@/api/order";
export default {
name: "Orderlist",
components: { Pagination },
data() {
return {
active: "follow",
types: {
0: "",
1: "",
2: "",
3: "primary",
4: "success",
5: "warning",
6: "danger",
7: "info",
},
types2: { 1: "primary", 2: "success", 3: "warning" },
status_arr: ["待跟进", "跟进中", "已核销", "核销失败", "放弃跟单"],
type_arr: ["-", "收益", "支出"],
timetype_arr: {},
order_status: [
"#9e9f9c",
"#04bcd9",
"#fc9904",
"#1193f4",
"#48b14b",
"#eb1662",
"#9d1cb5",
],
follow_status: [
"#9e9f9c",
"#04bcd9",
"#fc9904",
"#1193f4",
"#48b14b",
"#eb1662",
],
options: [],
value: null,
next_follow: null,
list: [],
total: 0,
listLoading: true,
listQuery: {
page: 1,
limit: 10,
times: [],
status: null,
admin: null,
zhubo: null,
os_status: [],
appointment_status: "",
},
item: { next_follow: "", personnel: { adult: "" } },
follow: [],
dialogVisible: false,
dialog2Visible: false,
applyVisible: false,
oss: [],
isSynchronization: false,
item3: {
sn: null,
backs: null,
flowObj: "",
os: null, // 12 3
},
os_arr: { 1: "美团", 2: "快手", 3: "抖音(甄选)", 5: "抖音(新国旅)" },
multipleSelection: [],
sn: [],
adminList: [],
form: {},
isAll: false,
rules: {
flowObj: [
{ required: true, message: "请选择流转对象", trigger: "change" },
],
},
};
},
async created() {
// this.listQuery.status = this.$route.query.status || null
this.listQuery.zhubo = this.$route.query.zhubo || null;
if (this.$route.query.start && this.$route.query.end) {
this.listQuery.times = [this.$route.query.start, this.$route.query.end];
}
// console.log(typeof this.$route.params.id);
this.setQuery("status");
this.setQuery("os_status");
this.setQuery("times");
this.setQuery("appointment_status");
// await this.getList();
// if (!this.$route.query.id) {
// await this.setOneClickRepair();
// await this.getList();
// }
this.getShortcutContent();
this.getAdminList();
},
mounted() {
console.log(this.$route.query);
this.setMode();
if (this.$route.query.id) {
this.onInfo({ id: this.$route.query.id });
}
},
computed: {
tableMaxHeight() {
return window.innerHeight - 320 + "px";
},
},
watch: {
$route(to, from) {
this.onInfo({ id: this.$route.query.id });
},
},
methods: {
handleSelectionChange(val) {
// console.log(val);
this.multipleSelection = val;
const data = [];
this.multipleSelection.map((item) => {
data.push(item.sn);
});
this.sn = data;
},
setQuery(key) {
if (this.$route.query.hasOwnProperty(key)) {
this.listQuery[key] = this.$route.query[key];
} else {
this.listQuery[key] = "";
}
},
async setMode() {
await this.getList();
// await this.setOneClickRepair();
// await this.getList();
},
async setOneClickRepair() {
let arr = this.list.map((res) => {
return res.id;
});
await this.onOneClickRepair({ id: arr.join() });
},
async getList($is_excel) {
this.listQuery.excel = null;
this.listQuery.is_refunded = 1;
if ($is_excel == 1) {
this.listQuery.excel = 1;
const isdate = this.listQuery.times[0] instanceof Date;
const params = {
...this.listQuery,
times: [
isdate ? this.listQuery.times[0].toISOString() : "",
isdate ? this.listQuery.times[1].toISOString() : "",
],
};
window.open("/admin/order/index?" + this.objectToQuery(params));
return;
}
await this.$axios
.get("/admin/order/index", { params: this.listQuery })
.then((response) => {
this.list = response.data.data;
this.total = response.data.total;
(this.timetype_arr = response.ext.timetype),
(this.oss = response.ext.oss);
this.listLoading = false;
this.isSynchronization = true;
});
},
objectToQuery(obj) {
return Object.keys(obj)
.map((key) => {
const value = obj[key];
if (value == undefined || value == null) return "";
return encodeURIComponent(key) + "=" + encodeURIComponent(value);
})
.join("&");
},
onInfo(item) {
this.value = null;
this.next_follow = null;
this.$set(item, "next_follow", null);
// this.item = item;
this.active = "follow";
this.$axios
.get("/admin/order/info", { params: { id: item.id } })
.then((res) => {
this.item = res.data;
this.dialogVisible = true;
})
.catch((err) => {});
},
resetForm(formName) {
this.$refs[formName].resetFields();
},
getAdminList(typeDesc = "") {
this.$axios
.get("/admin/admin/index", {
params: { limit: 100, status: 1, is_order: 1, type_desc: typeDesc },
})
.then((response) => {
this.adminList = response.data.data;
this.listLoading = false;
})
.catch((err) => {});
},
onCirculation(item) {
this.getAdminList(item.category_desc);
this.applyVisible = true;
this.isAll = false;
this.item3 = { ...item, os: Number(item.os) };
console.log(this.item3);
if (this.item3.backs && this.item3.backs.admin_id) {
this.item3.flowObj = this.item3.backs.admin_id;
} else {
this.resetForm("ruleForm");
}
},
//
onCirculationAll() {
this.applyVisible = true;
this.isAll = true;
},
//
onCirculationSave(to_admin_id) {
this.$refs.ruleForm.validate(async (valid) => {
if (valid) {
let res;
if (this.isAll) {
this.$confirm("是否批量流转订单", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
res = await orderbackBatch({
sn: this.sn,
to_admin_id: to_admin_id,
});
if (res.data) {
this.$message({
message: "批量流转订单成功",
type: "success",
});
this.applyVisible = false;
this.isAll = false;
this.getList();
}
});
} else {
res = await orderBack({
sn: this.item3.sn,
os: this.item3.os,
to_admin_id: to_admin_id,
});
if (res.data) {
this.$message({
message: "流转订单成功",
type: "success",
});
this.applyVisible = false;
this.isAll = false;
this.getList();
}
}
} else {
return false;
}
});
},
//
onCancel() {
this.$refs.ruleForm.validate((valid) => {
if (valid) {
this.$axios
.post("/admin/order/backcancel", { id: this.item3.id })
.then((res) => {
this.applyVisible = false;
this.isAll = false;
this.getList();
})
.catch((err) => {
console.log(err);
});
} else {
return false;
}
});
},
onBack() {
this.$axios
.post("/admin/order/back", this.item)
.then((res) => {
this.dialogVisible = false;
this.item = {};
this.getList();
})
.catch((err) => {});
},
onSave(item) {
this.$axios
.post("/admin/order/save", {
id: item.id,
check_sn: item.check_sn,
is_wechat: item.is_wechat,
travel_end: item.travel_end,
travel_date: item.travel_date,
desc: item.desc,
status: item.status,
next_follow: this.next_follow,
personnel: this.item.personnel,
})
.then((res) => {
this.dialogVisible = false;
this.item = { next_follow: "", personnel: { adult: "" } };
this.$router.push({
path: "/order/index",
});
})
.catch((err) => {});
},
onPass(form) {
this.$axios
.post("/admin/order/pass", { check_sn: form.check_sn })
.then((res) => {
this.dialog2Visible = false;
this.form = {};
})
.catch((err) => {});
},
onChange(from) {
this.$set(
this.item,
"desc",
from + (this.item.desc != undefined ? this.item.desc : "")
);
},
onChange2(from) {
this.$set(
this.item,
"to_admin_id",
from + (this.item.admin_id != undefined ? this.item.admin_id : "")
);
},
handleChange(os) {
console.log(os);
},
getShortcutContent() {
this.listLoading = true;
this.$axios
.get("/admin/shortcutContent/list", {
params: { page: 1, limit: 50, status: 1 },
})
.then((response) => {
for (const r of response.data.data) {
this.options.push({ value: r.id, label: r.content });
}
})
.catch(() => {});
},
onOneClickRepair(item) {
// this.$axios
// .post("/admin/order/oneClickRepair", { id: item.id })
// .then((res) => {
// this.$notify({
// title: "",
// message: "",
// type: "success",
// });
// // this.getList();
// })
// .catch((err) => {
// this.$notify.error({
// title: "",
// message: err,
// });
// });
},
onOneClickYyHandle(item) {
this.$axios
.post("/admin/order/changeAppointmentStatus", { id: item.id })
.then((res) => {
this.dialogVisible = false;
this.$notify({
title: "成功",
message: "已处理",
type: "success",
});
// this.getList();
})
.catch((err) => {
this.$notify.error({
title: "错误",
message: err,
});
});
},
},
};
</script>
<style scoped>
.app-container {
position: relative;
padding-bottom: 60px; /* 分页条的高度 */
}
.filter-container,
.el-table {
padding-bottom: 5px; /* 分页条的高度,以避免内容重叠 */
}
</style>

View File

@ -578,7 +578,7 @@ export default {
flowObj: "", flowObj: "",
os: null, // 12 3 os: null, // 12 3
}, },
os_arr: { 1: "美团", 2: "快手", 3: "抖音" }, os_arr: { 1: "美团", 2: "快手", 3: "抖音(甄选)", 5:"抖音(新国旅)"},
adminList: [], adminList: [],
form: {}, form: {},
rules: { rules: {

View File

@ -3,9 +3,19 @@
<el-row> <el-row>
<el-col :span="24"> <el-col :span="24">
<div class="problem_form"> <div class="problem_form">
<el-form :inline="true" ref="form" :model="dataForm" label-width="60px"> <el-form
:inline="true"
ref="form"
:model="dataForm"
label-width="60px"
>
<el-form-item label="关键字:"> <el-form-item label="关键字:">
<el-input v-model="dataForm.keyword" placeholder="请输入搜索关键字" style="width: 400px;" class="filter-item" /> <el-input
v-model="dataForm.keyword"
placeholder="请输入搜索关键字"
style="width: 400px"
class="filter-item"
/>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="success" @click="onSubmit">查询</el-button> <el-button type="success" @click="onSubmit">查询</el-button>
@ -15,26 +25,58 @@
</el-col> </el-col>
</el-row> </el-row>
<div class="problem_container"> <div class="problem_container">
<div class="problem_left" v-infinite-scroll="load" :infinite-scroll-immediate="false" style="overflow:auto;padding: 10px;"> <div
<div @click="handleQacityl(item.city_id)" class="btn" v-for="item in getQaCityList">{{ item.city_name }}</div> class="problem_left"
v-infinite-scroll="load"
:infinite-scroll-immediate="false"
style="overflow: auto; padding: 10px"
>
<div
@click="handleQacityl(item.city_id)"
class="btn"
v-for="item in getQaCityList"
>
{{ item.city_name }}
</div>
</div> </div>
<div class="problem_right"> <div class="problem_right">
<ul class="infinite-list" v-infinite-scroll="load" :infinite-scroll-immediate="false" style="overflow:auto"> <ul
class="infinite-list"
v-infinite-scroll="load"
:infinite-scroll-immediate="false"
style="overflow: auto"
>
<li class="problem_right_container" v-for="item in getQaLists"> <li class="problem_right_container" v-for="item in getQaLists">
<div class="title"> <div class="title">
<span v-html="handleprant(item.title)"></span> <span v-html="handleprant(item.title)"></span>
<!-- <el-button @click="handleZip(item.img_zip)" type="primary">下载图片</el-button> <!-- <el-button @click="handleZip(item.img_zip)" type="primary">下载图片</el-button>
<el-button @click="handleZip(item.trip_zip)" type="primary">下载行程</el-button> --> <el-button @click="handleZip(item.trip_zip)" type="primary">下载行程</el-button> -->
<el-button @click="showImgDialog(item.img_zip)" type="primary">下载图片</el-button> <el-button @click="showImgDialog(item.img_zip)" type="primary"
<el-button @click="showFileList(item.trip_zip)" type="primary" style="margin-left: 10px;">下载行程</el-button> >下载图片</el-button
>
<el-button
@click="showFileList(item.trip_zip)"
type="primary"
style="margin-left: 10px"
>下载行程</el-button
>
</div> </div>
<div class="desc_container" v-for="list in item.qaQuestions"> <div class="desc_container" v-for="list in item.qaQuestions">
<span style="font-weight: 700;color: #46a6ff;" class="desc" v-html="handleprant(list.title)"> <span
style="font-weight: 700; color: #46a6ff"
class="desc"
v-html="handleprant(list.title)"
>
</span> </span>
<el-button @click="copyToClipboard(list.content)" type="primary" size="mini" style="margin-left: 10px;">复制</el-button> <el-button
@click="copyToClipboard(list.content)"
type="primary"
size="mini"
style="margin-left: 10px"
>复制</el-button
>
<div class="desc" v-html="handleprantHtml(list.content)"> <div class="desc" v-html="handleprantHtml(list.content)"></div>
</div>
</div> </div>
</li> </li>
</ul> </ul>
@ -50,10 +92,18 @@
class="image-card" class="image-card"
:body-style="{ padding: '10px' }" :body-style="{ padding: '10px' }"
> >
<img :src="image.url" class="image-preview" /> <div style="text-align: center">{{ image.desc }}</div>
<img :src="image.file" class="image-preview" />
<div class="image-footer"> <div class="image-footer">
<el-button @click="handlePreview(image.url)" size="mini">查看</el-button> <el-button @click="handlePreview(image.file)" size="mini"
<el-button @click="handleDownload(image.url)" type="success" size="mini">下载</el-button> >查看</el-button
>
<el-button
@click="handleDownload(image.file)"
type="success"
size="mini"
>下载</el-button
>
</div> </div>
</el-card> </el-card>
</div> </div>
@ -72,14 +122,22 @@
class="file-card" class="file-card"
:body-style="{ padding: '10px' }" :body-style="{ padding: '10px' }"
> >
<div class="file-info"> <div class="file-info">
<el-icon class="file-icon"> <el-icon class="file-icon">
<i :class="getFileIcon(file.url)"></i> <i :class="getFileIcon(file.file)"></i>
</el-icon> </el-icon>
<span class="file-name">{{ file.name }}</span> <span class="file-name" style="display: block;">{{ getFileName(file.file) }}</span>
</div>
<div class="file-info">
<span class="file-name">{{ file.desc }}</span>
</div> </div>
<div class="file-footer"> <div class="file-footer">
<el-button @click="handleDownload(file.url)" type="success" size="mini">下载</el-button> <el-button
@click="handleDownload(file.file)"
type="success"
size="mini"
>下载</el-button
>
</div> </div>
</el-card> </el-card>
</div> </div>
@ -99,7 +157,7 @@
</template> </template>
<script> <script>
import { getQaCityList, getQaList } from '@/api/qa' import { getQaCityList, getQaList } from "@/api/qa";
export default { export default {
data() { data() {
@ -107,110 +165,119 @@ export default {
getQaCityList: [], getQaCityList: [],
getQaLists: [], getQaLists: [],
dataForm: { dataForm: {
keyword: '', keyword: "",
city_id: '' city_id: "",
}, },
dialogImage: false, // dialogImage: false, //
previewVisible: false, // previewVisible: false, //
previewImageUrl: '', // URL previewImageUrl: "", // URL
previewTitle: '', // previewTitle: "", //
imageList: [ // imageList: [
//
], ],
fileList: [ // fileList: [
//
], ],
dialogFile: false, dialogFile: false,
} };
}, },
created() { created() {
getQaCityList().then(res => { getQaCityList().then((res) => {
this.getQaCityList = res.data this.getQaCityList = res.data;
}) });
}, },
watch: { watch: {
'dataForm.keyword': function (newVal) { "dataForm.keyword": function (newVal) {
if (newVal) { if (newVal) {
this.onSubmit(); this.onSubmit();
} }
} },
}, },
methods: { methods: {
// //
showFileList(fileUrl) { showFileList(fileUrl) {
this.fileList = []; if (fileUrl.length) {
if (fileUrl) { this.fileList = fileUrl;
this.fileList = fileUrl.split(',').map(url => ({ url, name: url.split('/').pop() }));
} }
this.dialogFile = true; this.dialogFile = true;
}, },
// URL // URL
getFileIcon(url) { getFileIcon(url) {
const ext = url.split('.').pop().toLowerCase(); const ext = url.split(".").pop().toLowerCase();
console.log('ext:' + ext);
switch (ext) { switch (ext) {
case 'pdf': case "pdf":
return 'el-icon-file-pdf'; return "el-icon-file-pdf";
case 'docx': case "docx":
return 'el-icon-file-word'; return "el-icon-file-word";
case 'pptx': case "pptx":
return 'el-icon-file-ppt'; return "el-icon-file-ppt";
case 'xlsx': case "xlsx":
return 'el-icon-file-excel'; return "el-icon-file-excel";
default: default:
return 'el-icon-file'; return "el-icon-file";
} }
}, },
// URL
getFileName(url) {
return url.substring(url.lastIndexOf("/") + 1);
},
handleQacityl(val) { handleQacityl(val) {
getQaList({ city_id: val }).then(res => { getQaList({ city_id: val }).then((res) => {
this.getQaLists = res.data.data this.getQaLists = res.data.data;
}) });
}, },
handleZip(url) { handleZip(url) {
if (url) { if (url) {
// window.open(url) // window.open(url)
fetch(url) fetch(url)
.then(response => response.blob()) .then((response) => response.blob())
.then(blob => { .then((blob) => {
const link = document.createElement('a'); const link = document.createElement("a");
const objectUrl = URL.createObjectURL(blob); const objectUrl = URL.createObjectURL(blob);
link.href = objectUrl; link.href = objectUrl;
link.download = url.split('/').pop(); // Extract filename from URL link.download = url.split("/").pop(); // Extract filename from URL
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
document.body.removeChild(link); document.body.removeChild(link);
URL.revokeObjectURL(objectUrl); URL.revokeObjectURL(objectUrl);
}) })
.catch(error => { .catch((error) => {
this.$message({ this.$message({
showClose: true, showClose: true,
message: '下载失败', message: "下载失败",
type: 'error' type: "error",
}); });
console.error('Download error:', error); console.error("Download error:", error);
}); });
} else { } else {
this.$message({ this.$message({
showClose: true, showClose: true,
message: '暂无下载链接', message: "暂无下载链接",
type: 'warning' type: "warning",
}); });
} }
}, },
load() { load() {
console.log('load') console.log("load");
}, },
handleprant(val) { handleprant(val) {
if (!val) return ''; // undefined if (!val) return ""; // undefined
let replaceReg = new RegExp(this.dataForm.keyword, "ig"); let replaceReg = new RegExp(this.dataForm.keyword, "ig");
let replaceString = `<span style="color: #fff;background-color: #FC0421FF;">${this.dataForm.keyword}</span>`; let replaceString = `<span style="color: #fff;background-color: #FC0421FF;">${this.dataForm.keyword}</span>`;
return val.replace(replaceReg, replaceString); return val.replace(replaceReg, replaceString);
}, },
handleprantHtml(val) { handleprantHtml(val) {
if (!val) return ''; if (!val) return "";
let p = this.dataForm.keyword; let p = this.dataForm.keyword;
let regexp = new RegExp(p, "g"); let regexp = new RegExp(p, "g");
let replacedHtml = val.replace(/(?<=>)[^>]+(?=<[/]?\w+.*>)/g, (v) => { let replacedHtml = val.replace(/(?<=>)[^>]+(?=<[/]?\w+.*>)/g, (v) => {
return v.replace(regexp, `<span style='color: #fff;background-color: #FC0421FF;'>${p}</span>`); return v.replace(
regexp,
`<span style='color: #fff;background-color: #FC0421FF;'>${p}</span>`
);
}); });
// //
if (replacedHtml === val) { if (replacedHtml === val) {
@ -220,32 +287,31 @@ export default {
return replacedHtml; return replacedHtml;
}, },
onSubmit() { onSubmit() {
getQaList(this.dataForm).then(res => { getQaList(this.dataForm).then((res) => {
this.getQaLists = res.data.data this.getQaLists = res.data.data;
}) });
}, },
copyToClipboard(html) { copyToClipboard(html) {
const text = this.stripHtml(html); const text = this.stripHtml(html);
const input = document.createElement('textarea'); const input = document.createElement("textarea");
input.value = text; input.value = text;
document.body.appendChild(input); document.body.appendChild(input);
input.select(); input.select();
document.execCommand('copy'); document.execCommand("copy");
document.body.removeChild(input); document.body.removeChild(input);
this.$message({ this.$message({
showClose: true, showClose: true,
message: '内容已复制' message: "内容已复制",
}); });
}, },
stripHtml(html) { stripHtml(html) {
const div = document.createElement('div'); const div = document.createElement("div");
div.innerHTML = html; div.innerHTML = html;
return div.textContent || div.innerText || ''; return div.textContent || div.innerText || "";
}, },
showImgDialog(imagesUrls) { showImgDialog(imagesUrls) {
this.imageList = [] if (imagesUrls.length) {
if (imagesUrls) { this.imageList = imagesUrls;
this.imageList = imagesUrls.split(',').map(url => ({ url }));
} }
this.dialogImage = true; this.dialogImage = true;
@ -253,7 +319,7 @@ export default {
// //
handlePreview(url) { handlePreview(url) {
this.previewImageUrl = url; this.previewImageUrl = url;
this.previewTitle = '图片预览'; this.previewTitle = "图片预览";
this.previewVisible = true; this.previewVisible = true;
}, },
// //
@ -265,35 +331,35 @@ export default {
if (url) { if (url) {
// window.open(url) // window.open(url)
fetch(url) fetch(url)
.then(response => response.blob()) .then((response) => response.blob())
.then(blob => { .then((blob) => {
const link = document.createElement('a'); const link = document.createElement("a");
const objectUrl = URL.createObjectURL(blob); const objectUrl = URL.createObjectURL(blob);
link.href = objectUrl; link.href = objectUrl;
link.download = url.split('/').pop(); // Extract filename from URL link.download = url.split("/").pop(); // Extract filename from URL
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
document.body.removeChild(link); document.body.removeChild(link);
URL.revokeObjectURL(objectUrl); URL.revokeObjectURL(objectUrl);
}) })
.catch(error => { .catch((error) => {
this.$message({ this.$message({
showClose: true, showClose: true,
message: '下载失败', message: "下载失败",
type: 'error' type: "error",
}); });
console.error('Download error:', error); console.error("Download error:", error);
}); });
} else { } else {
this.$message({ this.$message({
showClose: true, showClose: true,
message: '暂无下载链接', message: "暂无下载链接",
type: 'warning' type: "warning",
}); });
} }
} },
} },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -365,7 +431,7 @@ body {
font-weight: 600; font-weight: 600;
margin-bottom: 10px; margin-bottom: 10px;
color: #46a6ff; color: #46a6ff;
&>:nth-child(1) { & > :nth-child(1) {
margin-right: 40px; margin-right: 40px;
} }
} }

View File

@ -74,7 +74,7 @@
type="primary" type="primary"
size="small" size="small"
icon="el-icon-edit" icon="el-icon-edit"
@click="onEdit(scope.row)" @click="onAdd(scope.row)"
>编辑</el-button >编辑</el-button
> >
<el-button <el-button
@ -96,264 +96,185 @@
@pagination="getList" @pagination="getList"
/> />
<el-dialog title="添加QA" :visible.sync="dialogCreate"> <el-dialog
<el-form label-width="120px" :model="anchors"> v-if="dialogCreate"
<el-form-item label="城市"> ref="dialog"
<el-select v-model="anchors.city_id" placeholder="请选择"> :title="title"
<el-form-item :visible.sync="dialogCreate"
style="display: inline-flex; text-align: left; width: 770px" >
> <div
<el-option style="display: flex; justify-content: flex-end; margin-bottom: 10px"
v-for="item in getQaCitys" >
:key="item.city_id"
style="display: inline-flex; word-break: break-all"
:label="item.city_name"
:value="item.city_id"
/>
</el-form-item>
</el-select>
</el-form-item>
<el-form-item label="旅游路线">
<el-input
v-model="anchors.title"
type="text"
placeholder="请输入旅游路线"
/>
</el-form-item>
<el-form-item label="QA内容">
<div
class="mistake-content"
v-for="(item, index) in anchors.qaQuestions"
>
<div class="mistake-left">
<div>副标题</div>
<div class="qa-desc">
<el-input
style="width: 100px; margin-right: 10px"
v-model="item.sort"
type="text"
placeholder="序号"
/>
<el-input
v-model="item.title"
type="text"
placeholder="请输入副标题"
/>
</div>
<div>内容</div>
<div style="border: 1px solid #ccc">
<myEditor v-model="item.content" />
</div>
</div>
<div class="mistake-right">
<el-button @click="handleDel(index)" type="danger"
>删除</el-button
>
</div>
</div>
<div class="mistake-btn">
<el-button type="primary" @click="handleAdd">添加</el-button>
</div>
</el-form-item>
<el-form-item label="状态">
<el-switch
v-model="anchors.status"
:active-value="1"
:inactive-value="0"
active-color="#13ce66"
inactive-color="#ff4949"
/>
</el-form-item>
<el-form-item label="上传图片">
<el-upload
class="avatar-uploader"
action="admin/upload/index"
:show-file-list="false"
:on-success="handleAvatarSuccess"
>
<div v-for="(item, index) in anchors.img_zip" class="img-box">
<i
@click.stop="handleClose('img_zip', index)"
class="close el-icon-close"
/>
<i v-if="!checkIfUrlContainsImage(item)" class="el-icon-folder" />
<img
v-else
style="width: 100px; height: 100px"
:src="item"
class="avatar"
alt=""
/>
<div class="desc">{{ handleRegex(item) }}</div>
</div>
<i class="el-icon-plus avatar-uploader-icon" />
</el-upload>
<div style="color: red">(请上传.jpg, png的图片)</div>
</el-form-item>
<el-form-item label="上传行程">
<el-upload
class="avatar-uploader"
action="admin/upload/index"
:show-file-list="false"
:on-success="handleSuccess"
>
<div v-for="(item, index) in anchors.trip_zip" class="img-box">
<i
@click.stop="handleClose('trip_zip', index)"
class="close el-icon-close"
/>
<i v-if="!checkIfUrlContainsImage(item)" class="el-icon-folder" />
<img
v-else
style="width: 100px; height: 100px"
:src="item"
class="avatar"
alt=""
/>
<div class="desc">{{ handleRegex(item) }}</div>
</div>
<i class="el-icon-plus avatar-uploader-icon" />
</el-upload>
<span style="color: red">(本行程请上传,ppt,word,pdf格式的文件)</span>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button v-loading="loading" type="primary" @click="onSave" <el-button v-loading="loading" type="primary" @click="onSave"
> </el-button > </el-button
> >
</div> </div>
</el-dialog> <el-scrollbar ref="scrollbar" class="scrollable-container">
<el-form ref="addForm" label-width="120px" :model="anchors">
<el-dialog title="编辑内容" :visible.sync="dialogEdit"> <el-form-item label="城市" prop="city_id">
<el-form label-width="120px" :model="anchors"> <el-select v-model="anchors.city_id" placeholder="请选择">
<el-form-item label="城市"> <el-form-item
<el-select v-model="anchors.city_id" placeholder="请选择"> style="display: inline-flex; text-align: left; width: 770px"
<el-form-item
style="display: inline-flex; text-align: left; width: 770px"
>
<el-option
v-for="item in getQaCitys"
:key="item.city_id"
style="
width: 250px;
display: inline-flex;
word-break: break-all;
"
:label="item.city_name"
:value="item.city_id"
/>
</el-form-item>
</el-select>
</el-form-item>
<el-form-item label="旅游路线">
<el-input
v-model="anchors.title"
type="text"
placeholder="请输入旅游路线"
/>
</el-form-item>
<el-form-item label="QA内容">
<div
class="mistake-content"
v-for="(item, index) in anchors.qaQuestions"
>
<div class="mistake-left">
<div>副标题</div>
<div class="qa-desc">
<el-input
style="width: 100px; margin-right: 10px"
v-model="item.sort"
type="text"
placeholder="序号"
/>
<el-input
v-model="item.title"
type="text"
placeholder="请输入副标题"
/>
</div>
<div>内容</div>
<div style="border: 1px solid #ccc">
<myEditor v-model="item.content" />
</div>
</div>
<div class="mistake-right">
<el-button @click="handleDel(index)" type="danger"
>删除</el-button
> >
<el-option
v-for="item in getQaCitys"
:key="item.city_id"
style="display: inline-flex; word-break: break-all"
:label="item.city_name"
:value="item.city_id"
/>
</el-form-item>
</el-select>
</el-form-item>
<el-form-item label="旅游路线" prop="title">
<el-input
v-model="anchors.title"
type="text"
placeholder="请输入旅游路线"
/>
</el-form-item>
<el-form-item label="QA内容">
<div
class="mistake-content"
v-for="(item, index) in anchors.qaQuestions"
>
<div class="mistake-left">
<div>副标题</div>
<div class="qa-desc">
<el-input
style="width: 100px; margin-right: 10px"
v-model="item.sort"
type="text"
placeholder="序号"
/>
<el-input
v-model="item.title"
type="text"
placeholder="请输入副标题"
/>
</div>
<div>内容</div>
<div style="border: 1px solid #ccc">
<myEditor v-model="item.content" />
</div>
</div>
<div class="mistake-right">
<el-button @click="handleDel(index)" type="danger"
>删除</el-button
>
</div>
</div> </div>
</div> <div class="mistake-btn">
<div class="mistake-btn"> <el-button type="primary" @click="handleAdd">添加</el-button>
<el-button type="primary" @click="handleAdd">添加</el-button> </div>
</div> </el-form-item>
</el-form-item> <el-form-item label="状态">
<el-form-item label="状态"> <el-switch
<el-switch v-model="anchors.status"
v-model="anchors.status" :active-value="1"
:active-value="1" :inactive-value="0"
:inactive-value="0" active-color="#13ce66"
active-color="#13ce66" inactive-color="#ff4949"
inactive-color="#ff4949" />
/> </el-form-item>
</el-form-item> <div
<el-form-item label="上传图片"> style="
<el-upload display: flex;
class="avatar-uploader" justify-content: flex-end;
action="admin/upload/index" margin-bottom: 10px;
:show-file-list="false" "
:on-success="handleAvatarSuccess"
> >
<div v-for="(item, index) in anchors.img_zip" class="img-box"> <el-button type="primary" @click="onAddImg()">添加图片</el-button>
<i </div>
@click.stop="handleClose('img_zip', index)" <el-form-item label="上传图片">
class="close el-icon-close" <div class="upload-list">
/> <div class="wu-yu" v-for="(item, index) in anchors.img_zip">
<i v-if="!checkIfUrlContainsImage(item)" class="el-icon-folder" /> <i
<img @click.stop="handleClose('img_zip', index)"
v-else class="close el-icon-close"
style="width: 100px; height: 100px" />
:src="item" <el-upload
class="avatar" class="avatar-uploader"
alt="" action=""
/> :show-file-list="false"
<div class="desc">{{ handleRegex(item) }}</div> :http-request="handlesAvatarSuccess"
:on-success="
(response, file, fileList) =>
handleAvatarSuccess(response, file, fileList, index)
"
>
<div v-if="!!item.file" class="img-box">
<i
v-if="!checkIfUrlContainsImage(item.file)"
class="el-icon-folder"
/>
<img
v-else
style="width: 100px; height: 100px"
:src="item.file"
class="avatar"
alt=""
/>
<div class="desc">{{ handleRegex(item.file) }}</div>
</div>
<i v-else class="el-icon-plus avatar-uploader-icon" />
</el-upload>
<el-input v-model="item.desc" placeholder="图片说明"></el-input>
</div>
</div> </div>
<i class="el-icon-plus avatar-uploader-icon" /> <div style="color: red">(请上传.jpg, png的图片)</div>
</el-upload> </el-form-item>
<div style="color: red">(请上传.jpg, png的图片)</div> <div
</el-form-item> style="
<el-form-item label="上传行程"> display: flex;
<el-upload justify-content: flex-end;
class="avatar-uploader" margin-bottom: 10px;
action="admin/upload/index" "
:show-file-list="false"
:on-success="handleSuccess"
> >
<div v-for="(item, index) in anchors.trip_zip" class="img-box"> <el-button type="primary" @click="onAddtrip()">添加行程</el-button>
<i </div>
@click.stop="handleClose('trip_zip', index)" <el-form-item label="上传行程">
class="close el-icon-close" <div class="upload-list">
/> <div class="wu-yu" v-for="(item, index) in anchors.trip_zip">
<i v-if="!checkIfUrlContainsImage(item)" class="el-icon-folder" /> <i
<img @click.stop="handleClose('trip_zip', index)"
v-else class="close el-icon-close"
style="width: 100px; height: 100px" />
:src="item" <el-upload
class="avatar" class="avatar-uploader"
alt="" action=""
/> :http-request="handlesAvatarSuccess"
<div class="desc">{{ handleRegex(item) }}</div> :show-file-list="false"
:on-success="
(response, file, fileList) =>
handleSuccess(response, file, fileList, index)
"
>
<div v-if="!!item.file" class="img-box">
<i
v-if="!checkIfUrlContainsImage(item.file)"
class="el-icon-folder"
/>
<img
v-else
style="width: 100px; height: 100px"
:src="item.file"
class="avatar"
alt=""
/>
<div class="desc">{{ handleRegex(item.file) }}</div>
</div>
<i v-else class="el-icon-plus avatar-uploader-icon" />
</el-upload>
<el-input v-model="item.desc" placeholder="行程说明"></el-input>
</div>
</div> </div>
<i class="el-icon-plus avatar-uploader-icon" /> <span style="color: red"
</el-upload> >(本行程请上传,ppt,word,pdf格式的文件)</span
<span style="color: red">(本行程请上传,ppt,word,pdf格式的文件)</span> >
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> </el-scrollbar>
<el-button v-loading="loading" type="primary" @click="onSave"
> </el-button
>
</div>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
@ -361,13 +282,14 @@
<script> <script>
import Pagination from "@/components/PaginationFixed"; import Pagination from "@/components/PaginationFixed";
import myEditor from "@/components/Wangeditor/index.vue"; import myEditor from "@/components/Wangeditor/index.vue";
import { getToken } from "@/utils/auth";
export default { export default {
name: "getQa", name: "getQa",
components: { Pagination, myEditor }, components: { Pagination, myEditor },
data() { data() {
return { return {
statusArr: { 0: "禁用", 1: "启用" }, statusArr: { 0: "禁用", 1: "启用" },
title: "",
list: [], list: [],
total: 0, total: 0,
loading: false, loading: false,
@ -387,8 +309,8 @@ export default {
item: {}, item: {},
anchors: { anchors: {
qaQuestions: [], qaQuestions: [],
img_zip: [], img_zip: [{ desc: "", file: "" }],
trip_zip: [], trip_zip: [{ desc: "", file: "" }],
}, },
getQaCitys: {}, getQaCitys: {},
}; };
@ -411,19 +333,42 @@ export default {
".svg", ".svg",
".webp", ".webp",
]; ];
console.log(url);
console.log(
"========fffff====" +
imageExtensions.some((extension) =>
url.toLowerCase().endsWith(extension)
)
);
console.log(url);
return imageExtensions.some((extension) => return imageExtensions.some((extension) =>
url.toLowerCase().endsWith(extension) url.toLowerCase().endsWith(extension)
); );
}, },
async handlesAvatarSuccess(file) {
try {
var formdata = new FormData();
formdata.append("file", file.file);
this.upLoading = true;
const _this = this;
const res = await this.$axios.post("/admin/upload/index", formdata, {
headers: {
"Content-type": "multipart/form-data",
"X-Token": getToken(),
},
});
// if (![200].includes(res.data.code)) {
// this.$message.error("");
// return;
// }
// success
file.onSuccess(res);
} catch (error) {
console.error(error);
}
},
handleClose(val, i) { handleClose(val, i) {
if (this.anchors[val].length == 1) {
this.$message({
message: "至少保留一条",
type: "warning",
});
return;
}
console.log("i===", i); console.log("i===", i);
this.anchors[val].splice(i, 1); this.anchors[val].splice(i, 1);
}, },
@ -461,19 +406,27 @@ export default {
this.listLoading = false; this.listLoading = false;
}); });
}, },
handleAvatarSuccess(res, file) { onAddImg() {
console.log("====1111===" + res); this.anchors.img_zip.push({ desc: "", file: "" });
this.anchors.img_zip.push(
`${window.location.protocol}//${window.location.host}${res.data}`
);
console.log("====111122222===" + this.anchors.img_zip);
}, },
handleSuccess(res, file) { onAddtrip() {
console.log("====222222===" + res); this.anchors.trip_zip.push({ desc: "", file: "" });
this.anchors.trip_zip.push( },
`${window.location.protocol}//${window.location.host}${res.data}` handleAvatarSuccess(res, file, fileList, index) {
); if (!res.data) return;
console.log("====111122222===" + this.anchors.trip_zip); this.anchors.img_zip[
index
].file = `${window.location.protocol}//${window.location.host}${res.data}`;
// this.anchors.img_zip.push(
// `${window.location.protocol}//${window.location.host}${res.data}`
// );
},
handleSuccess(res, file, fileList, index) {
console.log(res, file, fileList);
if (!res.data) return;
this.anchors.trip_zip[
index
].file = `${window.location.protocol}//${window.location.host}${res.data}`;
}, },
handleRegex(val) { handleRegex(val) {
const regex = /\/([^\/]+)$/; const regex = /\/([^\/]+)$/;
@ -484,51 +437,54 @@ export default {
this.listQuery.page = 1; this.listQuery.page = 1;
this.getList(); this.getList();
}, },
onAdd() { onAdd(item) {
this.anchors.qaQuestions = [
{
sort: 1,
title: "",
content: "",
},
];
// 0
this.anchors.img_zip = [];
this.anchors.trip_zip = [];
this.dialogCreate = true; this.dialogCreate = true;
}, if (!item.id) {
onEdit(item) { this.title = "添加QA";
if (!item.qaQuestions.length) { this.anchors.qaQuestions = [
this.anchors = { {
...item, sort: 1,
qaQuestions: [ title: "",
{ content: "",
sort: 1, },
title: "", ];
content: "", // 0
}, this.anchors.img_zip = [{ desc: "", file: "" }];
], this.anchors.trip_zip = [{ desc: "", file: "" }];
};
} else { } else {
this.anchors = { ...item }; this.title = "编辑QA";
if (!item.qaQuestions.length) {
this.anchors = {
...item,
qaQuestions: [
{
sort: 1,
title: "",
content: "",
},
],
};
} else {
this.anchors = { ...item };
}
this.anchors.img_zip = item.img_zip
? item.img_zip
: [{ desc: "", file: "" }];
this.anchors.trip_zip = item.trip_zip
? item.trip_zip
: [{ desc: "", file: "" }];
} }
this.anchors.img_zip = item.img_zip;
this.anchors.trip_zip = item.trip_zip;
// this.anchors.img_zip =
// typeof item.img_zip == "string" ? [item.img_zip] : item.img_zip;
// this.anchors.trip_zip =
// typeof item.trip_zip == "string" ? [item.trip_zip] : item.trip_zip;
this.dialogEdit = true;
}, },
onSave() { onSave() {
if (this.loading) return; if (this.loading) return;
this.loading = true; this.loading = true;
const api = this.dialogCreate ? "/admin/qa/addQa" : "/admin/qa/editQa"; const api =
this.title == "添加QA" ? "/admin/qa/addQa" : "/admin/qa/editQa";
this.$axios this.$axios
.post(api, this.anchors) .post(api, this.anchors)
.then(() => { .then(() => {
this.dialogCreate = false; this.dialogCreate = false;
this.dialogEdit = false; // this.dialogEdit = false;
this.loading = false; this.loading = false;
this.getList(); this.getList();
}) })
@ -585,11 +541,45 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.scrollable-container {
// position: relative;
height: 500px; /* 可以根据实际情况调整高度 */
// overflow-y: auto;
}
::v-deep.el-scrollbar .el-scrollbar__wrap {
overflow-x: hidden;
}
.upload-list {
display: flex;
flex-wrap: wrap;
.wu-yu {
margin-bottom: 10px;
position: relative;
display: flex;
width: 110px;
flex-wrap: wrap;
align-content: space-between;
& + .wu-yu {
margin-left: 5px;
}
::v-deep.el-input {
width: 100px;
margin: 10px 0 10px 10px;
}
}
}
.filter-items {
position: fixed;
// right: 0;
left: 70%;
z-index: 66;
}
.img-box { .img-box {
position: relative; position: relative;
display: inline-block; display: inline-block;
width: 100px; width: 100px;
margin-bottom: 10px; margin-bottom: 10px;
margin-left: 10px;
& + .img-box { & + .img-box {
margin-left: 10px; margin-left: 10px;
} }
@ -615,7 +605,7 @@ export default {
.close { .close {
position: absolute; position: absolute;
top: -10px; top: -10px;
right: -8px; right: -15px;
font-size: 18px; font-size: 18px;
color: #409eff; color: #409eff;
} }

View File

@ -0,0 +1,279 @@
<template>
<div class="app-container">
<div class="scheduling_head">
<div class="head_lable">产品编号:</div>
<div
class="scheduling_tab"
@click="handleClick(index)"
:key="index"
:class="activeName == item.id ? 'active' : ''"
v-for="(item, index) in liveroomList"
>
{{ item.name }}
</div>
<el-button type="primary" style="margin-left: 210px" @click="handleEdit"
>编辑</el-button
>
</div>
<div class="scheduling_tab_pana">
<div class="table-list" v-for="item in roomWorksList">
<div class="table-list_item">
<div class="tit">
{{ item.date }}
</div>
</div>
<div class="table-list_item">
<div class="tit">
{{ item.date_name
}}<i
@click="handleAdds('', item.date)"
class="el-icon-circle-plus"
></i>
</div>
</div>
<div class="table-list-box" v-for="it in item.work">
<div class="border" @dblclick="handleAdds(it, item.date)">
<div style="margin-bottom: 10px">
AM {{ extractTime(it.start) }}~{{ extractTime(it.end) }}
</div>
<div>主播{{ it.zhubo.name }}</div>
<div>中控{{ it.zhongkong.name }}</div>
<div>路线{{ it.route ? it.route : "无" }}</div>
</div>
</div>
</div>
</div>
<liveroom-update
v-if="addOrUpdateVisible"
ref="addOrUpdate"
@update="saveProducts"
></liveroom-update>
<roomWorksUpdate
v-if="roomVisible"
ref="roomUpdate"
:title="title"
@update="saveRoomWorks"
></roomWorksUpdate>
</div>
</template>
<script>
import {
liveroom,
roomWorks,
availableZhubo,
saveProducts,
saveRoomWorks,
} from "@/api/scheduling";
import liveroomUpdate from "./liveroom-update.vue";
import roomWorksUpdate from "./room-works";
export default {
name: "Scheduling",
data() {
return {
list: [],
activeName: "",
listLoading: true,
title: "添加排班",
listQuery: {
page: 1,
limit: 20,
title: "",
status: "",
},
total: 0,
dialogVisible: false,
dialogType: "new",
liveroomList: [],
roomWorksList: [],
product_ids: "",
live_room_id: "",
addOrUpdateVisible: false,
roomVisible: false,
};
},
components: {
liveroomUpdate,
roomWorksUpdate,
},
async mounted() {
await this.liveroom();
this.roomWorks();
},
methods: {
extractTime(dateTimeStr) {
//
const regex = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/;
const match = dateTimeStr.match(regex);
if (!match) {
throw new Error("不匹配");
}
//
return `${match[4]}:${match[5]}:${match[6]}`;
},
isCurrentTimeAfter(startTimeStr) {
// Date
const startTime = new Date(startTimeStr);
//
const currentTime = new Date();
//
return currentTime >= startTime;
},
//
async liveroom() {
try {
let { data } = await liveroom();
this.liveroomList = data;
this.activeName = this.liveroomList[0].id + "";
this.live_room_id = this.liveroomList[0].id;
this.product_ids = this.liveroomList[0].product_ids;
} catch (err) {
console.log(err);
}
},
//
async roomWorks() {
try {
let { data } = await roomWorks(this.live_room_id);
this.roomWorksList = data;
} catch (err) {
console.log(err);
}
},
// tab
handleClick(i) {
let { id, product_ids } = this.liveroomList[i];
this.live_room_id = id;
this.product_ids = product_ids;
this.activeName = id + "";
this.roomWorks();
},
//
handleEdit() {
this.addOrUpdateVisible = true;
this.$nextTick(() => {
this.$refs["addOrUpdate"].init(this.live_room_id, this.product_ids);
});
},
//
handleAdds(form, date) {
console.log(form);
this.title = form.id ? "编辑排班" : "添加排班";
if (form.id && this.isCurrentTimeAfter(form.start)) {
return this.$message.error("当前时间无法编辑");
}
this.roomVisible = true;
this.$nextTick(() => {
this.$refs["roomUpdate"].init(this.live_room_id, { ...form }, date);
});
},
saveProducts(form) {
this.liveroom();
saveProducts(form).then((res) => {
this.$message({
message: "直播间编辑成功",
type: "success",
});
});
},
saveRoomWorks(form) {
saveRoomWorks(form).then((res) => {
this.roomWorks();
this.$message({
message: "添加排班成功",
type: "success",
});
});
},
handleAdd() {},
},
};
</script>
<style lang="scss" scoped>
.app-container {
position: relative;
padding-bottom: 60px; /* 分页条的高度 */
}
::v-deep .el-tabs__nav-wrap::after {
display: none;
}
.filter-container,
.el-table {
padding-bottom: 5px; /* 分页条的高度,以避免内容重叠 */
}
::v-deep .el-row {
height: 50px;
line-height: 50px;
}
.head_lable {
font-size: 14px;
color: #3d3d3d;
font-weight: 700;
margin-right: 10px;
}
.scheduling_head {
display: flex;
align-items: center;
margin-bottom: 28px;
}
.scheduling_tab {
display: inline-block;
padding: 12px 28px;
font-size: 16px;
border: 1px solid #e2e2e2;
border-radius: 5px;
cursor: pointer;
& + .scheduling_tab {
margin-left: 10px;
}
}
.active {
border-color: #1269ff;
}
.el-icon-circle-plus {
color: #367af0;
font-size: 16px;
}
.scheduling_tab_pana {
display: flex;
justify-content: space-between;
// align-items: center;
.table-list {
flex-basis: 14.3%;
.table-list_item {
height: 56px;
background: #e4ecff;
display: flex;
// justify-content: center;
align-items: center;
border: 1px solid #d7ddeb;
margin-right: -1px;
margin-bottom: -1px;
.tit {
width: 100%;
display: flex;
justify-content: space-evenly;
}
}
.table-list-box {
padding: 15px;
font-size: 14px;
color: #ffffff;
border: 1px solid #d7ddeb;
margin-right: -1px;
margin-bottom: -1px;
.border {
padding: 13px;
background: #367af0;
cursor: pointer;
}
}
}
}
</style>

View File

@ -0,0 +1,51 @@
<template>
<el-dialog
title="编辑直播间"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose"
>
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="直播间id" prop="product_ids">
<el-input v-model="form.product_ids"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="handleEdit"> </el-button>
</span>
</el-dialog>
</template>
<script>
export default {
props: {},
data() {
return {
dialogVisible: false,
form: {
product_ids: "",
live_room_id: "",
},
};
},
methods: {
init(live_room_id, product_ids) {
this.dialogVisible = true;
this.form.product_ids = product_ids;
this.form.live_room_id = live_room_id;
console.log(this.form);
},
handleClose() {
this.dialogVisible = false;
},
handleEdit() {
this.dialogVisible = false;
this.$emit("update", this.form);
},
},
};
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,208 @@
<template>
<el-dialog
:title="title"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose"
>
<el-form :rules="rules" ref="form" :model="form" label-width="80px">
<el-form-item label="直播间" prop="live_room_id">
<el-select v-model="form.live_room_id" placeholder="请选择直播间">
<el-option
v-for="item in liveroomList"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<!-- <el-form-item label="开始时间" prop="start">
<el-date-picker
@change="handleDate"
v-model="form.start"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetime"
placeholder="选择日期时间"
>
</el-date-picker>
</el-form-item>
<el-form-item label="结束时间" prop="end">
<el-date-picker
v-model="form.end"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetime"
@change="handleDate"
placeholder="选择日期时间"
>
</el-date-picker>
</el-form-item> -->
<el-form-item label="时间">
<el-time-select
placeholder="起始时间"
v-model="startTime"
:picker-options="{
start: '08:00',
step: '00:15',
end: '24:00',
}"
>
</el-time-select>
<el-time-select
placeholder="结束时间"
@change="handleDate"
v-model="endTime"
:picker-options="{
start: '08:00',
step: '00:15',
end: '24:00',
minTime: startTime,
}"
>
</el-time-select>
</el-form-item>
<el-form-item label="主播" prop="zhubo_id">
<el-select v-model="form.zhubo_id" placeholder="请选择主播">
<el-option
v-for="item in zhuboList"
:label="item.name"
:value="item.id"
:disabled="item.disabled"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="中控" prop="zhongkong_id">
<el-select v-model="form.zhongkong_id" placeholder="请选择中控">
<el-option
v-for="item in zhongkongList"
:label="item.name"
:value="item.id"
:disabled="item.disabled"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="路线">
<el-input v-model="form.route"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="handleEdit"> </el-button>
</span>
</el-dialog>
</template>
<script>
import { availableZhubo, liveroom } from "@/api/scheduling";
export default {
props: {
title: {
type: String,
default: "添加排班",
},
},
data() {
return {
dialogVisible: false,
startTime: "",
endTime: "",
form: {
live_room_id: "",
start: "",
end: "",
zhubo_id: "",
zhongkong_id: "",
route: "",
},
zhuboList: [],
zhongkongList: [],
liveroomList: [],
dayDate: "",
rules: {
live_room_id: [
{ required: true, message: "请选择直播间", trigger: "change" },
],
zhongkong_id: [
{ required: true, message: "请选择中控", trigger: "change" },
],
zhubo_id: [
{ required: true, message: "请选择主播", trigger: "change" },
],
// date: [{ required: true, message: "", trigger: "change" }],
},
};
},
watch: {
form: function () {
this.handleDate();
},
},
methods: {
extractTime(dateTimeStr) {
//
const regex = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/;
const match = dateTimeStr.match(regex);
if (!match) {
throw new Error("不匹配");
}
//
return `${match[4]}:${match[5]}`;
},
init(live_room_id, form, date) {
this.dialogVisible = true;
this.$nextTick((res) => {
this.$refs["form"].resetFields();
this.dayDate = date;
this.form = !!form.id
? form
: {
live_room_id: "",
start: "",
end: "",
zhubo_id: "",
zhongkong_id: "",
};
this.form.live_room_id = live_room_id;
this.startTime = form.id ? this.extractTime(this.form.start) : "";
this.endTime = form.id ? this.extractTime(this.form.end) : "";
console.log(this.startTime);
console.log(this.endTime);
});
this.liveroom();
},
//
async liveroom() {
try {
let { data } = await liveroom();
this.liveroomList = data;
} catch (err) {
console.log(err);
}
},
handleClose() {
this.dialogVisible = false;
},
handleDate() {
this.form.end = `${this.dayDate} ${this.endTime}:00`;
this.form.start = `${this.dayDate} ${this.startTime}:00`;
if (this.form.end && this.form.start) {
availableZhubo({ ...this.form, type: 2 }).then((res) => {
this.zhuboList = res.data;
});
availableZhubo({ ...this.form, type: 3 }).then((res) => {
this.zhongkongList = res.data;
});
}
},
handleEdit() {
// this.form.end = `${this.dayDate} ${this.startTime}:00`;
// this.form.start = `${this.dayDate} ${this.endTime}:00`;
this.dialogVisible = false;
this.$emit("update", this.form);
},
},
};
</script>
<style scoped lang="scss"></style>

View File

@ -40,7 +40,7 @@ module.exports = {
"/dev-api": { "/dev-api": {
// 接口地址 以 api开头的都走下面的配置 // 接口地址 以 api开头的都走下面的配置
// target: 'https://www.szjinao.cn', // 代理目标地址为后端服务器地址 127.0.0.1 192.168.1.2 // target: 'https://www.szjinao.cn', // 代理目标地址为后端服务器地址 127.0.0.1 192.168.1.2
target: "http://192.168.0.100:8787", // 代理目标地址为后端服务器地址 127.0.0.1 192.168.1.2 target: 'http://192.168.0.100:8787', // 代理目标地址为后端服务器地址 127.0.0.1 192.168.1.2
ws: true, // 是否支持 websocket 请求 支持 ws: true, // 是否支持 websocket 请求 支持
changeOrigin: true, // 是否启用跨域 changeOrigin: true, // 是否启用跨域
pathRewrite: { pathRewrite: {

View File

@ -4,6 +4,7 @@ namespace app\admin\controller;
use app\model\Admins; use app\model\Admins;
use app\model\Onlines; use app\model\Onlines;
use app\model\Orders; use app\model\Orders;
use app\model\Products;
use support\Log; use support\Log;
use support\Request; use support\Request;
use support\Redis; use support\Redis;
@ -26,8 +27,8 @@ class AdminController extends base
} }
if($typeDesc = $request->get('type_desc')) { if($typeDesc = $request->get('type_desc')) {
$routeType = self::ROUTE_LISTS[mb_substr($typeDesc, 0, 2)] ?? 10; //$routeType = self::ROUTE_LISTS[mb_substr($typeDesc, 0, 2)] ?? 10;
$query->where('route_type', $routeType); //$query->where('route_type', $routeType);
} }
$list = $query->paginate($request->get('limit',10)); $list = $query->paginate($request->get('limit',10));
@ -72,6 +73,11 @@ class AdminController extends base
$is_franchisee = $request->post('is_franchisee',0); $is_franchisee = $request->post('is_franchisee',0);
$product_ids = $request->post('product_ids',''); $product_ids = $request->post('product_ids','');
$routeType = $request->post('route_type','10'); $routeType = $request->post('route_type','10');
$type = $request->post('type','0');
if (in_array($type, [2, 3])) { // 主播/中控默认密码123456
$password = '123456';
}
if($id) { if($id) {
$item = (new Admins())->find($id); $item = (new Admins())->find($id);
@ -112,6 +118,7 @@ class AdminController extends base
$item->is_franchisee = $is_franchisee; $item->is_franchisee = $is_franchisee;
$item->product_ids = $product_ids; $item->product_ids = $product_ids;
$item->route_type = $routeType; $item->route_type = $routeType;
$item->type = $type;
Log::info('logs:' . json_encode($item)); Log::info('logs:' . json_encode($item));
$back = $item->save(); $back = $item->save();
if($back) if($back)
@ -177,6 +184,11 @@ class AdminController extends base
$list[$adminId]['order_num'] = $user->order_num; $list[$adminId]['order_num'] = $user->order_num;
$list[$adminId]['id'] = $adminId; $list[$adminId]['id'] = $adminId;
$list[$adminId]['username'] = $user->username; $list[$adminId]['username'] = $user->username;
$list[$adminId]['route_type'] = $user->route_type;
$list[$adminId]['routes'] = [];
if ($user->product_ids) {
$list[$adminId]['routes'] = Products::query()->field(['product_name'])->whereIn('id', explode(',', $user->product_ids))->select();
}
} }
return $this->success(array_values($list)); return $this->success(array_values($list));

View File

@ -11,10 +11,16 @@ use support\Request;
class DataController extends base class DataController extends base
{ {
public function index(Request $request) { /**
$times = $request->get('times'); * 获取订单统计数据
$os = $request->get('os'); * @param $times
* @param $os
* @return Orders[]|array|\think\Collection|\think\db\Query[]|\think\model\Collection
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
private function orderStatics($times, $os) {
$query = Orders::alias('o')->with('admin') $query = Orders::alias('o')->with('admin')
->leftJoin('admins a','a.id = o.admin_id') ->leftJoin('admins a','a.id = o.admin_id')
->order('o.admin_id','desc') ->order('o.admin_id','desc')
@ -23,19 +29,19 @@ class DataController extends base
count(o.id) as orders, count(o.id) as orders,
SUM(o.total_price) as total_price, SUM(o.total_price) as total_price,
SUM(CASE SUM(CASE
WHEN (o.os = 1 AND o.order_status = 4) OR (o.os = 3 AND o.order_status = 2) OR (o.os = 2 AND o.order_status = 5) THEN 1 WHEN (o.os in (1,7) AND o.order_status = 4) OR (o.os in(3,5) AND o.order_status = 2) OR (o.os = 2 AND o.order_status = 5) THEN 1
ELSE 0 ELSE 0
END) as assets, END) as assets,
SUM(CASE SUM(CASE
WHEN (o.os = 1 AND o.order_status = 4) OR (o.os = 3 AND o.order_status = 2) OR (o.os = 2 AND o.order_status = 5) THEN asset_price WHEN (o.os in (1,7) AND o.order_status = 4) OR (o.os in(3,5) AND o.order_status = 2) OR (o.os = 2 AND o.order_status = 5) THEN asset_price
ELSE 0 ELSE 0
END) as asset_price, END) as asset_price,
SUM(CASE SUM(CASE
WHEN (o.os = 1 AND o.order_status = 5) OR (o.os = 3 AND o.order_status = 4) OR (o.os = 2 AND o.order_status = 1) THEN 1 WHEN (o.os in (1,7) AND o.order_status = 5) OR (o.os in(3,5) AND o.order_status = 4) OR (o.os = 2 AND o.order_status = 1) THEN 1
ELSE 0 ELSE 0
END) AS nopays, END) AS nopays,
SUM(CASE SUM(CASE
WHEN (o.os = 1 AND o.order_status = 5) OR (o.os = 3 AND o.order_status = 4) OR (o.os = 2 AND o.order_status = 1) THEN actual_price WHEN (o.os in (1,7) AND o.order_status = 5) OR (o.os in(3,5) AND o.order_status = 4) OR (o.os = 2 AND o.order_status = 1) THEN actual_price
ELSE 0 ELSE 0
END) AS nopay_price, END) AS nopay_price,
o.admin_id,a.name'); o.admin_id,a.name');
@ -50,8 +56,12 @@ class DataController extends base
$query->where('o.os', $os); $query->where('o.os', $os);
} }
$list = $query->select(); return $query->select();
}
public function index(Request $request) {
$times = $request->get('times');
$os = $request->get('os');
$list = $this->orderStatics($times, $os);
//统计每一列 //统计每一列
$totalArr[1] = [ $totalArr[1] = [
'write_rate' => 0, 'write_rate' => 0,
@ -63,6 +73,10 @@ class DataController extends base
'nopays' => 0, 'nopays' => 0,
'nopay_price' => 0, 'nopay_price' => 0,
'admin_id' => 0, 'admin_id' => 0,
'm_orders' => 0,
'm_total_price' => 0,
'm_assets' => 0,
'm_asset_price' => 0,
'name' => '合计', 'name' => '合计',
'admin' => [ 'admin' => [
'username' => '合计', 'username' => '合计',
@ -70,9 +84,29 @@ class DataController extends base
'avatar' => '', 'avatar' => '',
] ]
]; ];
// 当月数据统计
$currentList = $this->orderStatics([date('Y-m-01'), date('Y-m-d H:i:s')], $os);
$currentList = array_column($currentList->toArray(), null, 'admin_id');
foreach ($list as $k => $v) { foreach ($list as $k => $v) {
$list[$k]['write_rate'] = number_format(($v['assets']/$v['orders'])*100,2); $list[$k]['write_rate'] = number_format(($v['assets']/$v['orders'])*100,2);
$list[$k]['write_rate_price'] = number_format(($v['asset_price']/$v['total_price'])*100,2); $list[$k]['write_rate_price'] = number_format(($v['asset_price']/$v['total_price'])*100,2);
// 当月核销率(按订单)
$list[$k]['month_write_rate'] = 0;
// 当月核销率(按销售额)
$list[$k]['month_write_rate_price'] = 0;
if (isset($currentList[$v['admin_id']])) {
$currentAdmin = $currentList[$v['admin_id']];
$list[$k]['month_write_rate'] = number_format(($currentAdmin['assets']/$currentAdmin['orders'])*100,2);
$list[$k]['month_write_rate_price'] = number_format(($currentAdmin['asset_price']/$currentAdmin['total_price'])*100,2);
$totalArr[1]['m_orders'] += $currentAdmin['orders'];
$totalArr[1]['m_total_price'] += $currentAdmin['total_price'];
$totalArr[1]['m_assets'] += $currentAdmin['assets'];
$totalArr[1]['m_asset_price'] += $currentAdmin['asset_price'];
}
$totalArr[1]['orders'] += $list[$k]['orders']; $totalArr[1]['orders'] += $list[$k]['orders'];
$totalArr[1]['total_price'] += $list[$k]['total_price']; $totalArr[1]['total_price'] += $list[$k]['total_price'];
$totalArr[1]['assets'] += $list[$k]['assets']; $totalArr[1]['assets'] += $list[$k]['assets'];
@ -82,6 +116,8 @@ class DataController extends base
} }
$totalArr[1]['write_rate'] = (float)number_format(($totalArr[1]['assets']/$totalArr[1]['orders'])*100,2); $totalArr[1]['write_rate'] = (float)number_format(($totalArr[1]['assets']/$totalArr[1]['orders'])*100,2);
$totalArr[1]['write_rate_price'] = (float)number_format(($totalArr[1]['asset_price']/$totalArr[1]['total_price'])*100,2); $totalArr[1]['write_rate_price'] = (float)number_format(($totalArr[1]['asset_price']/$totalArr[1]['total_price'])*100,2);
$totalArr[1]['month_write_rate'] = (float)number_format(($totalArr[1]['m_assets']/$totalArr[1]['m_orders'])*100,2);
$totalArr[1]['month_write_rate_price'] = (float)number_format(($totalArr[1]['m_asset_price']/$totalArr[1]['m_total_price'])*100,2);
$list = array_merge($list->toArray(), $totalArr); $list = array_merge($list->toArray(), $totalArr);
$excel = $request->get('excel'); $excel = $request->get('excel');

View File

@ -64,7 +64,34 @@ class IndexController extends base
$backs = Backs::where('admin', $request->admin->id)->where('status', 0)->count(); $backs = Backs::where('admin', $request->admin->id)->where('status', 0)->count();
} }
return $this->success(['new' => $new ?? 0, 'follow' => $follow, 'back' => $backs]); // 核销提醒
$orderWriteOff = Redis::get('CRM:USER:WRITE:OFF:' . $request->admin->id);
Redis::del('CRM:USER:WRITE:OFF:' . $request->admin->id);
// 跟进提醒
$followMessage = 0;
$followOrderId = '';
$appointmentNum = 0;
if ($request->admin->id > 1) {
$start = strtotime(date('Y-m-d')) * 1000;
$end = strtotime(date('Y-m-d', strtotime('+1 days'))) * 1000 - 1;
$followMessageList = Orders::where(['admin_id' => $request->admin->id])
->whereBetween('next_follow', [$start, $end])
->where('status', 1)
->field(['id'])
->select();
$followMessage = count($followMessageList);
if ($followMessage) {
$followOrderId = $followMessageList[0]['id'];
}
// 预约未处理数量
$appointmentNum = Orders::where(['admin_id' => $request->admin->id])
->where('appointment_status', 1)
->count();
}
return $this->success(['new' => $new ?? 0, 'follow' => $follow, 'back' => $backs, 'order_write_off' => $orderWriteOff, 'follow_message' => $followMessage, 'follow_order_id' => $followOrderId, 'appointment_num' => $appointmentNum]);
} }
public function line() public function line()
@ -120,51 +147,51 @@ class IndexController extends base
SUM(total_price) as total, SUM(total_price) as total,
COUNT(id) as `all`, COUNT(id) as `all`,
SUM(CASE SUM(CASE
WHEN (os = 1 AND order_status = 3) OR (os = 2 AND order_status = 4) OR (os = 3 AND order_status = 1) THEN 1 WHEN (os in(1,7) AND order_status = 3) OR (os = 2 AND order_status = 4) OR (os in (3,5) AND order_status = 1) THEN 1
ELSE 0 ELSE 0
END) as tobeused, END) as tobeused,
SUM(CASE SUM(CASE
WHEN (os = 1 AND order_status = 3) OR (os = 2 AND order_status = 4) OR (os = 3 AND order_status = 1) THEN total_price WHEN (os in(1,7) AND order_status = 3) OR (os = 2 AND order_status = 4) OR (os in (3,5) AND order_status = 1) THEN total_price
ELSE 0 ELSE 0
END) as tobeused_price, END) as tobeused_price,
SUM(CASE SUM(CASE
WHEN (os = 1 AND order_status = 4) OR (os = 3 AND order_status = 2) OR (os = 2 AND order_status = 5) THEN 1 WHEN (os in(1,7) AND order_status = 4) OR (os in (3,5) AND order_status = 2) OR (os = 2 AND order_status = 5) THEN 1
ELSE 0 ELSE 0
END) as asset, END) as asset,
SUM(CASE SUM(CASE
WHEN (os = 1 AND order_status = 4) OR (os = 3 AND order_status = 2) OR (os = 2 AND order_status = 5) THEN asset_price WHEN (os in(1,7) AND order_status = 4) OR (os in (3,5) AND order_status = 2) OR (os = 2 AND order_status = 5) THEN asset_price
ELSE 0 ELSE 0
END) as asset_price, END) as asset_price,
SUM(CASE SUM(CASE
WHEN ((os = 1 AND status = 5) OR (os = 3 AND status = 4) OR (os = 2 AND status = 1)) THEN 1 WHEN ((os in(1,7) AND status = 5) OR (os in (3,5) AND status = 4) OR (os = 2 AND status = 1)) THEN 1
ELSE 0 ELSE 0
END) AS refund, END) AS refund,
SUM(CASE SUM(CASE
WHEN ((os = 1 AND status = 5) OR (os = 3 AND status = 4) OR (os = 2 AND status = 1)) THEN actual_price WHEN ((os in(1,7) AND status = 5) OR (os in (3,5) AND status = 4) OR (os = 2 AND status = 1)) THEN actual_price
ELSE 0 ELSE 0
END) AS refund_price, END) AS refund_price,
SUM(CASE SUM(CASE
WHEN ((os=1 AND order_status=3) OR (os=2 AND order_status=4) OR (os=3 AND order_status=1)) AND create_at >= ' . $startOfThirtyDaysAgoTimestamp . ' AND create_at <= ' . $endOfDayTimestamp . ' THEN 1 WHEN ((os in(1,7) AND order_status=3) OR (os=2 AND order_status=4) OR (os in (3,5) AND order_status=1)) AND create_at >= ' . $startOfThirtyDaysAgoTimestamp . ' AND create_at <= ' . $endOfDayTimestamp . ' THEN 1
ELSE 0 ELSE 0
END) AS tobeused_30, END) AS tobeused_30,
SUM(CASE SUM(CASE
WHEN ((os=1 AND order_status=3) OR (os=2 AND order_status=4) OR (os=3 AND order_status=1)) AND create_at >= ' . $startOfThirtyDaysAgoTimestamp . ' AND create_at <= ' . $endOfDayTimestamp . ' THEN total_price WHEN ((os in(1,7) AND order_status=3) OR (os=2 AND order_status=4) OR (os in (3,5) AND order_status=1)) AND create_at >= ' . $startOfThirtyDaysAgoTimestamp . ' AND create_at <= ' . $endOfDayTimestamp . ' THEN total_price
ELSE 0 ELSE 0
END) AS tobeused_price_30, END) AS tobeused_price_30,
SUM(CASE SUM(CASE
WHEN ((os=1 AND order_status=3) OR (os=2 AND order_status=4) OR (os=3 AND order_status=1)) AND create_at >= ' . $startOfSixtyDaysAgoTimestamp . ' AND create_at <= ' . $endOfDayTimestamp . ' THEN 1 WHEN ((os in(1,7) AND order_status=3) OR (os=2 AND order_status=4) OR (os in (3,5) AND order_status=1)) AND create_at >= ' . $startOfSixtyDaysAgoTimestamp . ' AND create_at <= ' . $endOfDayTimestamp . ' THEN 1
ELSE 0 ELSE 0
END) AS tobeused_60, END) AS tobeused_60,
SUM(CASE SUM(CASE
WHEN ((os=1 AND order_status=3) OR (os=2 AND order_status=4) OR (os=3 AND order_status=1)) AND create_at >= ' . $startOfSixtyDaysAgoTimestamp . ' AND create_at <= ' . $endOfDayTimestamp . ' THEN total_price WHEN ((os in(1,7) AND order_status=3) OR (os=2 AND order_status=4) OR (os in (3,5) AND order_status=1)) AND create_at >= ' . $startOfSixtyDaysAgoTimestamp . ' AND create_at <= ' . $endOfDayTimestamp . ' THEN total_price
ELSE 0 ELSE 0
END) AS tobeused_price_60, END) AS tobeused_price_60,
SUM(CASE SUM(CASE
WHEN ((os=1 AND order_status=3) OR (os=2 AND order_status=4) OR (os=3 AND order_status=1)) AND create_at >= ' . $startOfEightyDaysAgoTimestamp . ' AND create_at <= ' . $endOfDayTimestamp . ' THEN 1 WHEN ((os in(1,7) AND order_status=3) OR (os=2 AND order_status=4) OR (os in (3,5) AND order_status=1)) AND create_at >= ' . $startOfEightyDaysAgoTimestamp . ' AND create_at <= ' . $endOfDayTimestamp . ' THEN 1
ELSE 0 ELSE 0
END) AS tobeused_80, END) AS tobeused_80,
SUM(CASE SUM(CASE
WHEN ((os=1 AND order_status=3) OR (os=2 AND order_status=4) OR (os=3 AND order_status=1)) AND create_at >= ' . $startOfEightyDaysAgoTimestamp . ' AND create_at <= ' . $endOfDayTimestamp . ' THEN total_price WHEN ((os in(1,7) AND order_status=3) OR (os=2 AND order_status=4) OR (os in (3,5) AND order_status=1)) AND create_at >= ' . $startOfEightyDaysAgoTimestamp . ' AND create_at <= ' . $endOfDayTimestamp . ' THEN total_price
ELSE 0 ELSE 0
END) AS tobeused_price_80 END) AS tobeused_price_80
'); ');

View File

@ -0,0 +1,199 @@
<?php
namespace app\admin\controller;
use app\model\Admins;
use app\model\LiveRoom;
use app\model\LiveRoomWorks;
use app\model\Orders;
use app\model\Works;
use support\Request;
class LiveRoomController extends base {
/**
* 直播间列表
* @param Request $request
* @return \support\Response
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index(Request $request) {
$all = LiveRoom::select();
return $this->success($all);
}
/**
* 更新直播产品
* @param Request $request
* @return \support\Response
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function saveProducts(Request $request) {
$roomId = $request->post('live_room_id', '');
if (empty($roomId)) {
$this->error('请选择直播间');
}
$products = $request->post('product_ids', '');
$productIds = explode(',', $products);
foreach ($productIds as $productId) {
if (empty($productId)) {
continue;
}
$liveRoom = LiveRoom::whereRaw(sprintf('find_in_set(%s, `product_ids`)', $productId))->where('id', '!=', $roomId)->find();
if (!empty($liveRoom)) {
return $this->error(sprintf('产品ID:%s已经在%s存在', $productId, $liveRoom['name']));
}
}
LiveRoom::where(['id' => $roomId])->update(['product_ids' => $products]);
return $this->success([]);
}
/**
* 排班列表
* @param Request $request
* @return \support\Response
*/
public function roomWorks(Request $request)
{
$roomId = $request->get('live_room_id', '');
if (!$roomId) {
return $this->error('请选择直播间');
}
$weekDays = get_week_days();
foreach ($weekDays as &$day) {
$day['work'] = LiveRoomWorks::getDayWork($day['date'], $roomId);
}
return $this->success($weekDays);
}
/**
* 排班
* @param Request $request
* @return \support\Response
*/
public function saveRoomWorks(Request $request)
{
$id = $request->post('id', '0');
$roomId = $request->post('live_room_id', '');
$zhuboId = $request->post('zhubo_id', 0);
$zhongkongId = $request->post('zhongkong_id', 0);
$route = $request->post('route', '');
$start = $request->post('start');
$end = $request->post('end');
$month = date('Ym', strtotime($start));
$day = date('Ymd', strtotime($start));
if (strtotime($start) < time()) {
return $this->error(6000, '当前时间不可排班');
}
if (strtotime($start) >= strtotime($end)) {
return $this->error(6000, '开始时间不可大于结束时间');
}
// 检测当前日期是否已有排班
if (LiveRoomWorks::dayWorkIsExists($id, $roomId, $start, $end)) {
return $this->error(6001, '当前时间段已有排班');
}
// 检测当前主播当天是否已排班
if (LiveRoomWorks::zhuboWorkIsExists($id, $zhuboId, $day)) {
return $this->error(6002, '当前主播已排班');
}
$data = [
'live_room_id' => $roomId,
'zhubo_id' => $zhuboId,
'zhongkong_id' => $zhongkongId,
'start' => $start,
'end' => $end,
'month' => $month,
'day' => $day,
'route' => $route,
];
if ($id) {
$result = LiveRoomWorks::where('id', $id)->update($data);
} else {
$result = LiveRoomWorks::create($data);
}
return $this->success($result);
}
/**
* 可排班主播列表
* @param Request $request
* @return \support\Response
* @throws \think\db\exception\DbException
*/
public function availableZhubo(Request $request) {
$type = $request->get('type', 2);
$id = $request->get('id', 0);
$start = $request->get('start');
if (empty($start)) {
return $this->error('请选择时间');
}
$end = $request->get('end');
$day = date('Ymd', strtotime($start));
$query = Admins::where('status', 1)->where('type', $type);
$list = $query->field(['id', 'name', 'mobile'])->select();
// 获取已排班主播
$adminIds = LiveRoomWorks::inWorkAdmin($day)->column('zhubo_id');
foreach ($list as $val) {
$val->disabled = false;
if ($type == 2 && in_array($val->id, $adminIds)) {
$val->disabled = true;
}
}
return $this->success($list);
}
/**
* 主播业绩查询
* @param Request $request
* @return \support\Response
*/
public function zhuboStatistics(Request $request)
{
$start = $request->get('start', date('Y-m-01'));
$end = $request->get('end', date('Y-m-d'));
$times = $request->get('times');
if (!empty($times) && is_array($times)) {
$start = $times[0];
$end = $times[1];
}
// 姓名,职务,手机号,订单金额,直播时长
$mobile = $request->get('mobile', '');
$query = Admins::whereIn('type', [2, 3]);
if (!empty($mobile)) {
$query->where('mobile', $mobile);
}
$list = $query->order('id', 'desc')->paginate($request->get('limit', 10));
foreach ($list as $work) {
$work->orders = 0;
$work->total = 0;
$work->asset_total = 0;
$work->work_time = 0;
$statics = LiveRoomWorks::staticOrder($work->type, $work->id, $start, $end);
if ($statics) {
$work->orders = $statics->orders;
$work->total = $statics->total/100;
$work->asset_total = $statics->asset_total/100;
$work->work_time = $statics->work_time;
}
}
return $this->success($list);
}
public function test() {
$data = LiveRoomWorks::staticOrder(3, 38, '2024-09-17 16:00:00', '2024-09-18 15:59:59');
return $this->success($data);
}
}

View File

@ -4,11 +4,13 @@ require_once(__DIR__.'/xlsxwriter.class.php');
use app\model\Admins; use app\model\Admins;
use app\model\Backs; use app\model\Backs;
use app\model\Blacks;
use app\model\Follows; use app\model\Follows;
use app\model\Orders; use app\model\Orders;
use app\model\Logs; use app\model\Logs;
use app\model\ThirdMobileLogs; use app\model\ThirdMobileLogs;
use app\server\AliCloudApiService; use app\server\SMS;
use app\server\ThirdApiService;
use app\server\Orders as ServerOrders; use app\server\Orders as ServerOrders;
use stdClass; use stdClass;
use support\Log; use support\Log;
@ -24,11 +26,15 @@ class OrderController extends base
$timetype = $request->get('timetype'); $timetype = $request->get('timetype');
$times = $request->get('times'); $times = $request->get('times');
$os_status = $request->get('os_status'); $os_status = $request->get('os_status');
$isRefunded = $request->get('is_refunded', 0);
$where = []; $where = [];
if(!$request->admin->is_super) { if(!$request->admin->is_super) {
$where = [['admin_id','=', $request->admin->id]]; $where = [['admin_id','=', $request->admin->id]];
} else {
$where = [['admin_id', '>', 0]];
} }
$where[] = ['is_refunded', '=', $isRefunded];
if($sn) { if($sn) {
$where[] = ['sn','=', $sn]; $where[] = ['sn','=', $sn];
@ -47,9 +53,16 @@ class OrderController extends base
if($os_status) { if($os_status) {
if ($os_status[0] == array_search(Orders::OSS[4],Orders::OSS)){ if ($os_status[0] == array_search(Orders::OSS[4],Orders::OSS)){
$query->whereRaw(Orders::AllOssStatusSql[$os_status[1]]); if (isset($os_status[1]) && is_numeric($os_status[1])) {
$query->whereRaw(Orders::AllOssStatusSql[$os_status[1]]);
}
}else{ }else{
$query->where('os', $os_status[0] ?? 0 )->where('order_status', $os_status[1] ?? 0); if (isset($os_status[0]) && is_numeric($os_status[0])) {
$query->where('os', $os_status[0]);
}
if (isset($os_status[1]) && is_numeric($os_status[1])) {
$query->where('order_status', $os_status[1]);
}
} }
} }
@ -125,24 +138,27 @@ class OrderController extends base
$list = $orders->visible(['admin' => ['username','name','avatar']])->hidden(['check_sn'])->append(['order_status_name','status_name','os_name']); $list = $orders->visible(['admin' => ['username','name','avatar']])->hidden(['check_sn'])->append(['order_status_name','status_name','os_name']);
$localMobiles = [];
foreach ($list as &$value) { foreach ($list as &$value) {
$mobileInfo = $value['mobileInfo'] ?? ''; $mobileInfo = $value['mobileInfo'] ?? '';
if (empty($value['mobileInfo'])) { if (empty($value['mobileInfo'])) {
try { try {
$mobileInfo = (new AliCloudApiService())->getMobileArea($value['mobile']); if (isset($localMobiles[$value['mobile']])) {
// 防止重复查询第三方接口
$mobileInfo = $localMobiles[$value['mobile']];
} else {
$mobileInfo = (new ThirdApiService())->getMobileArea($value['mobile']);
$localMobiles[$value['mobile']] = $mobileInfo;
}
} catch (\Exception $exception) { } catch (\Exception $exception) {
$mobileInfo = []; $mobileInfo = [];
Log::info('查询手机归属地失败:' . $exception->getMessage()); Log::info('查询手机归属地失败:' . $exception->getMessage());
} }
} }
$value['mobileInfo'] = $mobileInfo; $value['mobileInfo'] = $mobileInfo;
// if (!empty($mobileInfo)) {
// $value['mobile'] = $value['mobile'] . sprintf('(%s-%s)', $mobileInfo['area'], $mobileInfo['originalIsp']);
// }
} }
$_oss = []; $_oss = [];
$_oss[] = [];
foreach (Orders::OSS as $key=>$os) { foreach (Orders::OSS as $key=>$os) {
$_oss[$key] = ['label' => $os,'value' => $key]; $_oss[$key] = ['label' => $os,'value' => $key];
if($key == 1) { if($key == 1) {
@ -153,6 +169,12 @@ class OrderController extends base
$statss = Orders::DouyinStatus; $statss = Orders::DouyinStatus;
}elseif ($key == 4) { }elseif ($key == 4) {
$statss = Orders::AllOssStatus; $statss = Orders::AllOssStatus;
}elseif($key ==5) {
$statss = Orders::DouyinStatus;
}elseif($key ==6) {
$statss = Orders::TongchengStatus;
}elseif($key ==6) {
$statss = Orders::OrderStatus;
} }
$_ch = []; $_ch = [];
@ -163,7 +185,7 @@ class OrderController extends base
$_oss[$key]['children'] = $_ch; $_oss[$key]['children'] = $_ch;
} }
return $this->success($list,null,['timetype'=> Orders::timeType, 'oss' => $_oss]); return $this->success($list,null,['timetype'=> Orders::timeType, 'oss' => array_values($_oss)]);
} }
public function all(Request $request) { public function all(Request $request) {
@ -239,9 +261,8 @@ class OrderController extends base
if(empty($desc) || empty($status)) return $this->error(2004, '跟进说明需要填写!'); if(empty($desc) || empty($status)) return $this->error(2004, '跟进说明需要填写!');
if(empty($personnel)) return $this->error(2004, '人员情况需要填写!'); if(empty($personnel)) return $this->error(2004, '人员情况需要填写!');
if(empty($travel_date)) return $this->error(2004, '出游时间需要填写!'); if(empty($travel_date)) return $this->error(2004, '出游时间需要填写!');
if ($status == 1){ if (in_array($status, [0, 1])){
if(empty($next_follow)) return $this->error(2004, '下次跟进时间需要填写!'); if(empty($next_follow)) return $this->error(2004, '下次跟进时间需要填写!');
} }
if ($status == 2){ if ($status == 2){
$next_follow = ''; $next_follow = '';
@ -372,6 +393,40 @@ class OrderController extends base
return $this->success($backs->append(['order_status_name','os_name']),null, Orders::OSS); return $this->success($backs->append(['order_status_name','os_name']),null, Orders::OSS);
} }
// 批量流转出
public function backBatch(Request $request) {
$sn = $request->post('sn');
if (empty($sn) || !is_array($sn)) {
return $this->error(2001, '请选择流转的订单');
}
$toAdminId = $request->post('to_admin_id');
if (!$toAdminId) {
return $this->error(2001, '请选择流转的客服');
}
// Orders::whereIn('sn', $sn)->update(['admin_id'=> $toAdminId]);
foreach ($sn as $value) {
$item = Orders::where('sn', $value)->find();
if($item->admin_id == $toAdminId) {
continue;
}
Backs::create([
'order_id' => $item->id,
'admin_id' => $toAdminId,
'admin' => $item->admin_id,
'apply_id' => $request->admin->id,
'status' => 1
]);
Orders::where('sn', $value)->update(['admin_id'=> $toAdminId]);
// 发送短信
\app\server\Orders::sendOrderSms($toAdminId, $item);
}
return $this->success(true);
}
//把订单拉回 //把订单拉回
public function back(Request $request) { public function back(Request $request) {
$sn = $request->post('sn'); $sn = $request->post('sn');
@ -568,6 +623,42 @@ class OrderController extends base
} }
} }
return $this->success(implode("\n", $flow));
}catch(\Exception $e) {
Log::info(sprintf('oneClickRepair error:%s, %s, %s', $e->getMessage(), $e->getFile(), $e->getLine()) );
return $this->error(2006, '出错了:' . $e->getMessage());
}
}
/**
* 预约处理
* @param Request $request
* @return \support\Response
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function changeAppointmentStatus(Request $request)
{
try {
$ids = $request->post('id', 0);
$idArr = explode(',', $ids);
$flow = [];
foreach ($idArr as $id) {
Log::info('同步订单ID' . $id);
$order = Orders::where('id', $id)->find();
if (empty($order)) {
return $this->error(2004, '记录没有找到.');
}
// 订单是待使用状态,先同步第三方状态
$order['appointment_status'] = 2;
$order->save();
}
return $this->success(implode("\n", $flow)); return $this->success(implode("\n", $flow));
}catch(\Exception $e) { }catch(\Exception $e) {
return $this->error(2006, '出错了:' . $e->getMessage()); return $this->error(2006, '出错了:' . $e->getMessage());

View File

@ -0,0 +1,63 @@
<?php
namespace app\admin\controller;
use app\model\Admins;
use app\model\Onlines;
use app\model\Orders;
use app\model\Products;
use support\Log;
use support\Request;
use support\Redis;
class ProductsController extends base {
/**
* 线路列表
* @param Request $request
* @return \support\Response
* @throws \think\db\exception\DbException
*/
public function list(Request $request) {
$query = Products::where('status', 1)->order('id', 'desc');
if($username = $request->get('username')) {
$query->where('username', $username);
}
if($status = $request->get('status')) {
$query->where('status', $status);
}
if($is_order = $request->get('is_order')) {
$query->where('is_order', $is_order);
}
$list = $query->paginate($request->get('limit',1000));
return $this->success($list,null,['oss' => array_values(array_map(function ($os, $k) {
return ['id' => $k, 'os' => $os];
}, Orders::OSS, array_keys(Orders::OSS)))]);
}
public function add(Request $request) {
if (!$request->post('os')) {
return $this->error(2001, '请选择平台.');
}
if (!$request->post('product_name')) {
return $this->error(2001, '线路名称必填');
}
if (!$request->post('third_product_id')) {
return $this->error(2001, '线路id必填');
}
$where = ['os' => $request->post('os'), 'third_product_id' => $request->post('third_product_id')];
$product = (new Products())->where($where)->find();
if (!empty($product)) {
return $this->error(2002, '线路已存在');
}
$product = new Products();
$product->os = $request->post('os');
$product->third_product_id = $request->post('third_product_id');
$product->product_name = $request->post('product_name');
$product->status = 1;
$product->save();
Log::info('product:' . json_encode($product));
return $this->success([]);
}
}

View File

@ -38,6 +38,18 @@ class QaController extends base
}]) }])
->paginate($limit); ->paginate($limit);
$list = json_decode(json_encode($list), true);
if (isset($list['data']) && is_array($list['data'])) {
foreach ($list['data'] as &$item) {
if (isset($item['img_zip']) && !empty($item['img_zip'])) {
$item['img_zip'] = json_decode($item['img_zip'], true);
}
if (isset($item['trip_zip']) && !empty($item['trip_zip'])) {
$item['trip_zip'] = json_decode($item['trip_zip'], true);
}
}
}
return $this->success($list); return $this->success($list);
} }
@ -65,8 +77,12 @@ class QaController extends base
$list = $list->paginate($request->get('limit',10))->toArray(); $list = $list->paginate($request->get('limit',10))->toArray();
foreach ($list['data'] as &$item) { foreach ($list['data'] as &$item) {
$item['img_zip'] = $item['img_zip'] ? explode(',', $item['img_zip']) : []; if (isset($item['img_zip']) && !empty($item['img_zip'])) {
$item['trip_zip'] = $item['trip_zip'] ? explode(',', $item['trip_zip']) : []; $item['img_zip'] = json_decode($item['img_zip'], true);
}
if (isset($item['trip_zip']) && !empty($item['trip_zip'])) {
$item['trip_zip'] = json_decode($item['trip_zip'], true);
}
} }
return $this->success($list); return $this->success($list);
@ -90,15 +106,22 @@ class QaController extends base
if (empty($post['city_id'])) return $this->error(2001, 'city_id data cannot be empty!'); if (empty($post['city_id'])) return $this->error(2001, 'city_id data cannot be empty!');
if (empty($post['title'])) return $this->error(2001, 'title data cannot be empty!'); if (empty($post['title'])) return $this->error(2001, 'title data cannot be empty!');
if ($post['trip_zip']) { if ($post['trip_zip'] && is_array($post['trip_zip'])) {
$post['trip_zip'] = implode(',', $post['trip_zip']); $post['trip_zip'] = json_encode($post['trip_zip']);
} }
if ($post['img_zip']) { if ($post['img_zip'] && is_array($post['img_zip'])) {
$post['img_zip'] = implode(',', $post['img_zip']); $post['img_zip'] = json_encode($post['img_zip']);
} }
$qaQuestion = $post['qaQuestions'] ?? null; $qaQuestion = $post['qaQuestions'] ?? null;
unset($post['qaQuestions']); unset($post['qaQuestions']);
if (isset($post['update_time'])) {
unset($post['update_time']);
}
if (isset($post['id'])) {
unset($post['id']);
}
$post['create_time'] = time();
try { try {
Db::transaction(function () use ($post,$qaQuestion){ Db::transaction(function () use ($post,$qaQuestion){
@ -130,11 +153,11 @@ class QaController extends base
unset($post['update_time']); unset($post['update_time']);
unset($post['qaQuestions']); unset($post['qaQuestions']);
if ($post['trip_zip']) { if (isset($post['trip_zip']) && $post['trip_zip']) {
$post['trip_zip'] = implode(',', $post['trip_zip']); $post['trip_zip'] = json_encode($post['trip_zip']);
} }
if ($post['img_zip']) { if (isset($post['img_zip']) && $post['img_zip']) {
$post['img_zip'] = implode(',', $post['img_zip']); $post['img_zip'] = json_encode($post['img_zip']);
} }
try { try {

View File

@ -0,0 +1,55 @@
<?php
namespace app\command;
use app\model\Admins;
use app\model\LiveRoomWorks;
use app\model\Orders;
use app\model\Sales;
use app\model\Works;
use Carbon\Carbon;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use think\facade\Db;
class LiveRoomWorkCommand extends Command
{
protected static $defaultName = 'LiveRoomWorkCommand';
protected static $defaultDescription = '直播间排班表处理';
protected function configure()
{
$this
->setName('LiveRoomWorkCommand')
->setDescription('直播间排班表处理')
->setDefinition(
new InputDefinition(array(
new InputOption('room_id', 'd', InputOption::VALUE_REQUIRED),
))
);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('LiveRoomWorkService start');
// 找出直播已结束的排班
$now = Carbon::now()->toDateTimeString();
$endRooms = LiveRoomWorks::where([['end', '<', $now]])->whereNull('orders')->select();
// 统计排班数据
foreach ($endRooms as $endRoom) {
$orderSum = Orders::where('live_room_work_id', $endRoom->id)
->fieldRaw('count(1) as order_num')
->fieldRaw('sum(total_price) as total_price')
->find();
LiveRoomWorks::where('id', $endRoom->id)->update(['orders' => $orderSum->order_num ?? 0, 'total' => $orderSum->total_price ?? 0]);
}
$output->writeln('LiveRoomWorkService end');
return 1;
}
}

View File

@ -2,8 +2,11 @@
namespace app\command; namespace app\command;
use app\admin\controller\AdminController;
use app\common\Error;
use app\model\Admins; use app\model\Admins;
use app\model\Blacks; use app\model\Blacks;
use app\model\FilterMobiles;
use app\model\Finances; use app\model\Finances;
use app\model\Logs; use app\model\Logs;
use app\model\Orders; use app\model\Orders;
@ -41,10 +44,10 @@ class SpiderDy extends Command
); );
} }
protected function users() protected function users($routeType)
{ {
if (count($this->_users) > 0) return $this->_users; // if (count($this->_users) > 0) return $this->_users;
$users = Admins::where('status', 1)->where('is_order', 1)->select(); $users = Admins::where('status', 1)->where('is_order', 1)->where('route_type', $routeType)->select();
$us = []; $us = [];
foreach ($users as $u) { foreach ($users as $u) {
$ru = Redis::get('CRM:USER:ONLINE:' . $u->id); $ru = Redis::get('CRM:USER:ONLINE:' . $u->id);
@ -60,11 +63,21 @@ class SpiderDy extends Command
protected $_redis_pool = []; protected $_redis_pool = [];
protected function poolUser($status = 0) protected function poolUser($status = 0, $categoryDesc = '')
{ {
$routeType = 10;
// 抖音境内外类型判断
$abroadKeyWords = ['曼谷', '港', '澳', '泰国', '普吉岛', '境外'];
foreach ($abroadKeyWords as $kw) {
if (mb_strpos($categoryDesc, $kw) !== false) {
$routeType = 20;
break;
}
}
$status .= $routeType;
if (empty($this->_redis_pool[$status])) { if (empty($this->_redis_pool[$status])) {
$this->_redis_pool[$status] = Redis::hGetAll('CRM:Pool:' . $status); $this->_redis_pool[$status] = Redis::hGetAll('CRM:Pool:' . $status);
$users = $this->users(); $users = $this->users($routeType);
$_users = []; $_users = [];
if (empty($this->_redis_pool[$status])) { if (empty($this->_redis_pool[$status])) {
foreach ($users as $user) { foreach ($users as $user) {
@ -137,18 +150,6 @@ class SpiderDy extends Command
return 1; return 1;
} }
// echo "状态1\n";
// $this->poolUser(1);
// echo "状态2\n";
// $this->poolUser(2);
// echo "状态3\n";
// $this->poolUser(3);
// echo "状态4\n";
// $this->poolUser(4);
// echo "状态5\n";
// $this->poolUser(5);
// return 1;
// $this->checks($output); // $this->checks($output);
// $output->writeln('CHECK spider:dy'); // $output->writeln('CHECK spider:dy');
@ -166,9 +167,14 @@ class SpiderDy extends Command
// // $this->_kuaishouOrder($start, $end, false); // // $this->_kuaishouOrder($start, $end, false);
// } // }
$start = date('Y-m-d 00:00:00', strtotime("-{$d} days")); // 8点20 开始分配订单
$end = date('Y-m-d 23:59:59'); date_default_timezone_set('Asia/Shanghai');
$this->orders($start, $end, false); $startTime = strtotime(date('Y-m-d 09:10'));
if (time() > $startTime) {
$start = date('Y-m-d 00:00:00', strtotime("-{$d} days"));
$end = date('Y-m-d 23:59:59');
$this->orders($start, $end, false);
}
if (date('H') >= 1 && date('H') <= 7) { if (date('H') >= 1 && date('H') <= 7) {
$this->reload($output); $this->reload($output);
@ -228,19 +234,15 @@ class SpiderDy extends Command
$order->is_check = 1; $order->is_check = 1;
$order->check_sn = $json->check_sn; $order->check_sn = $json->check_sn;
$back = $order->save(); $back = $order->save();
echo "订单在自己名下";
} else { } else {
// echo "订单在自己名下2 $order_id";
$order = Orders::where('sn', $order_id)->find(); $order = Orders::where('sn', $order_id)->find();
// print_r($order->toArray()); // print_r($order->toArray());
if (!empty($order) && $order->admin_id == 0) { if (!empty($order) && $order->admin_id == 0) {
echo '订单不在自己的名下,并且没有分人。';
$order->is_check = 1; $order->is_check = 1;
$order->check_sn = $json->check_sn; $order->check_sn = $json->check_sn;
$order->admin_id = $json->admin_id; $order->admin_id = $json->admin_id;
$back = $order->save(); $back = $order->save();
} elseif (!empty($order) && $order->admin_id !== $json->admin_id) { } elseif (!empty($order) && $order->admin_id !== $json->admin_id) {
echo '订单不在自己的名下,并且分人了。';
$item = new Orders(); $item = new Orders();
$item->os = 1; $item->os = 1;
$item->sn = $order->sn; $item->sn = $order->sn;
@ -299,7 +301,7 @@ class SpiderDy extends Command
{ {
foreach (Orders::OSS as $k => $os) { foreach (Orders::OSS as $k => $os) {
$pages = 6; $pages = 3;
$page = 1; $page = 1;
while (true) { while (true) {
@ -328,10 +330,10 @@ class SpiderDy extends Command
break;*/ break;*/
case 3: case 3:
// echo '==========pages='.$pages; case 5:
try { try {
$dou = new Douyin(); $dou = new Douyin($k);
$list = $dou->get($page, $start, $end); $list = $dou->get($page, $start, $end, '');
$pages = $dou->totalPage; $pages = $dou->totalPage;
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error(dirname(__FILE__) . __LINE__ . $e); Log::error(dirname(__FILE__) . __LINE__ . $e);
@ -344,31 +346,50 @@ class SpiderDy extends Command
} }
foreach ($list as $order) { foreach ($list as $order) {
// 过滤一日游
if ($order->os == 3 && strpos($order->category_desc, '一日游') !== false) {
Log::info("抖音 跳过订单:{$order->order_id}");
continue;
}
$item = Orders::where('os', $order->os)->where('sn', $order->sn)->find(); $item = Orders::where('os', $order->os)->where('sn', $order->sn)->find();
if (empty($item)) { if (empty($item)) {
$order->is_zhibo = $this->zhibo($order->product_name, $order->os); $order->is_zhibo = $this->zhibo($order->product_name, $order->os);
$oldMobile = orders::where("os", $order->os)->where("mobile", $order->mobile) if (FilterMobiles::isFilterMobile($order->mobile)) {
->where("create_time", '>=', time() - 24 * 3600 * 3)->find(); $admin_id = 0;
if (!empty($oldMobile)) {
$admin_id = $oldMobile->admin_id;
} else { } else {
$admin_id = $this->poolUser($order->orderStatus); $oldMobile = orders::where("os", $order->os)->where("mobile", $order->mobile)
->where("create_time", '>=', time() - 24 * 3600 * 3)->find();
if (!empty($oldMobile)) {
$admin_id = $oldMobile->admin_id;
} else {
// $admin_id = $this->poolUser($order->orderStatus, $order->category_desc);
try {
$admin_id = \app\server\Orders::poolUser($order->orderStatus, $order->product_id);
} catch (\Exception $exception) {
Log::info(sprintf('dy create order fail:%s, order_id:%s', $exception->getMessage(), $order->sn));
continue;
}
}
if (empty($admin_id)) return null;
} }
if (empty($admin_id)) return null;
$order->admin_id = $admin_id; $order->admin_id = $admin_id;
$order->give_time = time(); $order->give_time = time();
//判断是否需要发短信 //判断是否需要发短信
if (($order->os == 1 && array_key_exists($order->order_status, [1 => 1, 3 => 3])) || if ($order->order_status == 1) {
($order->os == 2 && array_key_exists($order->order_status, [2 => 2, 3 => 3, 4 => 4])) || $this->sms($admin_id, $order);
($order->os == 3 && $order->order_status == 1)) {
// $this->sms($admin_id, $order);
} }
$work = \app\server\Orders::getLiveRoomWork($order->create_at, $order->product_id);
if ($work) {
$order->live_room_work_id = $work->id;
}
//新获得一个用户的,提示管理员有新的订单 //新获得一个用户的,提示管理员有新的订单
Redis::incrBy('CRM:USER:ONLINE:NEW:' . $admin_id, 1); Redis::incrBy('CRM:USER:ONLINE:NEW:' . $admin_id, 1);
$item = new Orders(); $item = new Orders();
@ -381,6 +402,10 @@ class SpiderDy extends Command
} }
} }
if ($item->order_status !== 2 && $order->order_status == 2) {
Redis::incrBy('CRM:USER:WRITE:OFF:' . $item->admin_id, 1);
}
$back = $item->save($order->toArray()); $back = $item->save($order->toArray());
if ($back) { if ($back) {
@ -388,6 +413,10 @@ class SpiderDy extends Command
} }
} }
// 数据小于50, 结束
if (empty($list)) {
break;
}
$page++; $page++;
} }
} }
@ -397,7 +426,7 @@ class SpiderDy extends Command
{ {
$back = Redis::set('SpiderMt:reload:dy:lock', time(), 'EX', 3600 * 8, 'NX'); $back = Redis::set('SpiderMt:reload:dy:lock', time(), 'EX', 3600 * 8, 'NX');
if (!$back) return; if (!$back) return;
$orders = Orders::where('create_at', '<=', (time() - 15 * 24 * 3600) * 1000)->where('os', 3)->wherein('status', [1, 2, 3])->select(); $orders = Orders::where('create_at', '<=', (time() - 15 * 24 * 3600) * 1000)->whereIn('os', [3, 5])->wherein('status', [1, 2, 3])->select();
foreach ($orders as $order) { foreach ($orders as $order) {
$this->reloadStatus($order->sn, $order->os, $output); $this->reloadStatus($order->sn, $order->os, $output);
} }
@ -422,7 +451,8 @@ class SpiderDy extends Command
$it = $m->get(1, null, null, $item->sn); $it = $m->get(1, null, null, $item->sn);
break;*/ break;*/
case 3: case 3:
$m = new Douyin(); case 5:
$m = new Douyin($item->os);
$it = $m->get(1, null, null, $item->sn); $it = $m->get(1, null, null, $item->sn);
break; break;
@ -458,18 +488,14 @@ class SpiderDy extends Command
{ {
$user = Admins::cache(true)->where('id', $admin_id)->find(); $user = Admins::cache(true)->where('id', $admin_id)->find();
if ((!config('app.debug', true) || config('app.debug', true) === 'false') && (time() * 1000 - $order->create_at) / 1000 < 2 * 24 * 3600) { if ((!config('app.debug', true) || config('app.debug', true) === 'false') && (time() * 1000 - $order->create_at) / 1000 < 2 * 24 * 3600) {
echo "发送短信\n";
print_r([$order->mobile, 261607, ['title' => $order->product_name, 'mobile' => $user->mobile]]);
$has = Blacks::where('mobile', $order->mobile)->find(); $has = Blacks::where('mobile', $order->mobile)->find();
if (empty($has) && !empty($order->mobile)) { if (empty($has) && !empty($order->mobile)) {
SMS::juhe_sms_send($order->mobile, 261607, ['title' => $order->product_name, 'mobile' => $user->mobile]); SMS::juhe_sms_send($order->mobile, 261607, ['title' => $order->product_name, 'mobile' => $user->mobile]);
} else { } else {
echo "黑名单不发送短信\n";
sleep(10); sleep(10);
} }
} else { } else {
echo "不发送短信\n";
print_r([$order->mobile, 261607, ['title' => $order->product_name, 'mobile' => $user->mobile]]); print_r([$order->mobile, 261607, ['title' => $order->product_name, 'mobile' => $user->mobile]]);
} }
} }

View File

@ -2,8 +2,10 @@
namespace app\command; namespace app\command;
use app\admin\controller\AdminController;
use app\model\Admins; use app\model\Admins;
use app\model\Blacks; use app\model\Blacks;
use app\model\FilterMobiles;
use app\model\Finances; use app\model\Finances;
use app\model\Logs; use app\model\Logs;
use app\model\Orders; use app\model\Orders;
@ -42,10 +44,10 @@ class SpiderMt extends Command
); );
} }
protected function users() protected function users($routeType)
{ {
if (count($this->_users) > 0) return $this->_users; // if (count($this->_users) > 0) return $this->_users;
$users = Admins::where('status', 1)->where('is_order', 1)->select(); $users = Admins::where('status', 1)->where('is_order', 1)->where('route_type', $routeType)->select();
$us = []; $us = [];
foreach ($users as $u) { foreach ($users as $u) {
$ru = Redis::get('CRM:USER:ONLINE:' . $u->id); $ru = Redis::get('CRM:USER:ONLINE:' . $u->id);
@ -84,11 +86,13 @@ class SpiderMt extends Command
protected $_redis_pool = []; protected $_redis_pool = [];
protected function poolUser($status = 0) protected function poolUser($status = 0, $categoryDesc = '')
{ {
$routeType = AdminController::ROUTE_LISTS[mb_substr($categoryDesc, 0, 2)] ?? 10;
$status .= $routeType;
if (empty($this->_redis_pool[$status])) { if (empty($this->_redis_pool[$status])) {
$this->_redis_pool[$status] = Redis::hGetAll('CRM:Pool:' . $status); $this->_redis_pool[$status] = Redis::hGetAll('CRM:Pool:' . $status);
$users = $this->users(); $users = $this->users($routeType);
$_users = []; $_users = [];
if (empty($this->_redis_pool[$status])) { if (empty($this->_redis_pool[$status])) {
foreach ($users as $user) { foreach ($users as $user) {
@ -134,7 +138,7 @@ class SpiderMt extends Command
Redis::hIncrBy('CRM:Pool:' . $status, $username, 1); Redis::hIncrBy('CRM:Pool:' . $status, $username, 1);
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error(dirname(__FILE__) . __LINE__ . '没有可以分配的用户', (array)json_encode($e)); Log::error(dirname(__FILE__) . __LINE__ . '没有可以分配的用户', (array)json_encode($e));
throw new \Exception('没有可以分配的用户'); throw new \Exception('没有可以分配的用户' . $status . '--' . $categoryDesc);
} }
return $username; return $username;
} }
@ -158,21 +162,8 @@ class SpiderMt extends Command
if ($orderid) { if ($orderid) {
$this->reloadStatus($orderid, $os, $output); $this->reloadStatus($orderid, $os, $output);
return 1;
} }
// echo "状态1\n";
// $this->poolUser(1);
// echo "状态2\n";
// $this->poolUser(2);
// echo "状态3\n";
// $this->poolUser(3);
// echo "状态4\n";
// $this->poolUser(4);
// echo "状态5\n";
// $this->poolUser(5);
// return 1;
// $this->checks($output); // $this->checks($output);
// $output->writeln('CHECK spider:mt'); // $output->writeln('CHECK spider:mt');
@ -191,9 +182,13 @@ class SpiderMt extends Command
// // $this->_kuaishouOrder($start, $end, false); // // $this->_kuaishouOrder($start, $end, false);
// } // }
$start = date('Y-m-d 00:00:00', strtotime("-{$d} days")); date_default_timezone_set('Asia/Shanghai');
$end = date('Y-m-d 23:59:59'); $startTime = strtotime(date('Y-m-d 09:10'));
$this->orders($start, $end, false); if (time() > $startTime) {
$start = date('Y-m-d 00:00:00', strtotime("-{$d} days"));
$end = date('Y-m-d 23:59:59');
$this->orders($start, $end, $orderid);
}
if (date('H') >= 1 && date('H') <= 7) { if (date('H') >= 1 && date('H') <= 7) {
$this->reload($output); $this->reload($output);
@ -253,20 +248,16 @@ class SpiderMt extends Command
$order->is_check = 1; $order->is_check = 1;
$order->check_sn = $json->check_sn; $order->check_sn = $json->check_sn;
$back = $order->save(); $back = $order->save();
echo "订单在自己名下";
} else { } else {
// echo "订单在自己名下2 $order_id";
$order = Orders::where('sn', $order_id)->find(); $order = Orders::where('sn', $order_id)->find();
// print_r($order->toArray()); // print_r($order->toArray());
if (!empty($order) && $order->admin_id == 0) { if (!empty($order) && $order->admin_id == 0) {
echo '订单不在自己的名下,并且没有分人。';
$order->is_check = 1; $order->is_check = 1;
$order->check_sn = $json->check_sn; $order->check_sn = $json->check_sn;
$order->admin_id = $json->admin_id; $order->admin_id = $json->admin_id;
$order->give_time = time(); $order->give_time = time();
$back = $order->save(); $back = $order->save();
} elseif (!empty($order) && $order->admin_id !== $json->admin_id) { } elseif (!empty($order) && $order->admin_id !== $json->admin_id) {
echo '订单不在自己的名下,并且分人了。';
$item = new Orders(); $item = new Orders();
$item->os = 1; $item->os = 1;
$item->sn = $order->sn; $item->sn = $order->sn;
@ -326,7 +317,7 @@ class SpiderMt extends Command
{ {
foreach (Orders::OSS as $k => $os) { foreach (Orders::OSS as $k => $os) {
$pages = 6; $pages = 20;
$page = 1; $page = 1;
while (true) { while (true) {
@ -335,15 +326,11 @@ class SpiderMt extends Command
switch ($k) { switch ($k) {
case 1: case 1:
case 7:
try { try {
$mei = new Meituan(); $mei = new Meituan($k);
$list = $mei->get($page, $start, $end); $list = $mei->get($page, $start, $end, $order_id);
// $pages = $mei->totalPage; // $pages = $mei->totalPage;
if ($mei->totalPage === 1) {
$pages = 1;
}
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error(dirname(__FILE__) . __LINE__ . $e); Log::error(dirname(__FILE__) . __LINE__ . $e);
} }
@ -378,27 +365,42 @@ class SpiderMt extends Command
$item = Orders::where('os', $order->os)->where('sn', $order->sn)->find(); $item = Orders::where('os', $order->os)->where('sn', $order->sn)->find();
if (empty($item)) { if (empty($item)) {
$order->is_zhibo = $this->zhibo($order->product_name, $order->os); //$order->is_zhibo = $this->zhibo($order->product_name, $order->os);
$oldMobile = orders::where("os", $order->os)->where("mobile", $order->mobile) // 刷单账号
->where("create_time", '>=', time() - 24 * 3600 * 3)->find(); if (FilterMobiles::isFilterMobile($order->mobile)) {
$admin_id = 0;
if (!empty($oldMobile)) {
$admin_id = $oldMobile->admin_id;
} else { } else {
$admin_id = $this->poolUser($order->orderStatus); $oldMobile = orders::where("os", $order->os)->where("mobile", $order->mobile)
} ->where("create_time", '>=', time() - 24 * 3600 * 3)->find();
if (empty($admin_id)) return null; if (!empty($oldMobile)) {
$admin_id = $oldMobile->admin_id;
} else {
try {
// $admin_id = $this->poolUser($order->orderStatus, $order->category_desc);
$admin_id = \app\server\Orders::poolUser($order->orderStatus, $order->product_id);
} catch (\Exception $exception) {
Log::info(sprintf('mt create order fail:%s, order_id:%s', $exception->getMessage(), $order->sn));
continue;
}
}
if (empty($admin_id)) return null;
}
$order->admin_id = $admin_id; $order->admin_id = $admin_id;
$order->give_time = time(); $order->give_time = time();
//判断是否需要发短信 //判断是否需要发短信
if (($order->os == 1 && array_key_exists($order->order_status, [1 => 1, 3 => 3])) || if ((in_array($order->os, [1, 7]) && array_key_exists($order->order_status, [1 => 1, 3 => 3]))) {
($order->os == 2 && array_key_exists($order->order_status, [2 => 2, 3 => 3, 4 => 4])) || if ($admin_id > 0) {
($order->os == 3 && $order->order_status == 1)) { $this->sms($admin_id, $order);
$this->sms($admin_id, $order); }
} }
$work = \app\server\Orders::getLiveRoomWork($order->create_at, $order->product_id);
if ($work) {
$order->live_room_work_id = $work->id;
}
Log::info(sprintf('mt create order success order_id:%s', $order->sn));
//新获得一个用户的,提示管理员有新的订单 //新获得一个用户的,提示管理员有新的订单
Redis::incrBy('CRM:USER:ONLINE:NEW:' . $admin_id, 1); Redis::incrBy('CRM:USER:ONLINE:NEW:' . $admin_id, 1);
$item = new Orders(); $item = new Orders();
@ -411,6 +413,10 @@ class SpiderMt extends Command
} }
} }
if ($item->order_status !== 4 && $order->order_status == 4) {
Redis::incrBy('CRM:USER:WRITE:OFF:' . $item->admin_id, 1);
}
$back = $item->save($order->toArray()); $back = $item->save($order->toArray());
if ($back) { if ($back) {
@ -418,6 +424,10 @@ class SpiderMt extends Command
} }
} }
// 数据小于50, 结束
if (empty($list)) {
break;
}
$page++; $page++;
} }
} }
@ -440,11 +450,13 @@ class SpiderMt extends Command
$item = Orders::where($w)->find(); $item = Orders::where($w)->find();
if (empty($item)) { if (empty($item)) {
$output->writeln('没有找到订单'); $output->writeln('没有找到订单');
return;
} }
$it = null; $it = null;
switch ($item->os) { switch ($item->os) {
case 1: case 1:
$m = new Meituan(); case 7:
$m = new Meituan($item->os);
$it = $m->get(1, null, null, $item->sn); $it = $m->get(1, null, null, $item->sn);
break; break;
/* case 2: /* case 2:
@ -488,19 +500,16 @@ class SpiderMt extends Command
{ {
$user = Admins::cache(true)->where('id', $admin_id)->find(); $user = Admins::cache(true)->where('id', $admin_id)->find();
if ((!config('app.debug', true) || config('app.debug', true) === 'false') && (time() * 1000 - $order->create_at) / 1000 < 2 * 24 * 3600) { if ((!config('app.debug', true) || config('app.debug', true) === 'false') && (time() * 1000 - $order->create_at) / 1000 < 2 * 24 * 3600) {
echo "发送短信\n";
print_r([$order->mobile, 261607, ['title' => $order->product_name, 'mobile' => $user->mobile]]); print_r([$order->mobile, 261607, ['title' => $order->product_name, 'mobile' => $user->mobile]]);
$has = Blacks::where('mobile', $order->mobile)->find(); $has = Blacks::where('mobile', $order->mobile)->find();
if (empty($has) && !empty($order->mobile)) { if (empty($has) && !empty($order->mobile)) {
SMS::juhe_sms_send($order->mobile, 261607, ['title' => $order->product_name, 'mobile' => $user->mobile]); SMS::juhe_sms_send($order->mobile, 261607, ['title' => $order->product_name, 'mobile' => $user->mobile]);
} else { } else {
echo "黑名单不发送短信\n";
sleep(10); sleep(10);
} }
} else { } else {
echo "不发送短信\n"; Log::info('不发送短信' . json_encode([$order->mobile, 261607, ['title' => $order->product_name, 'mobile' => $user->mobile]]));
print_r([$order->mobile, 261607, ['title' => $order->product_name, 'mobile' => $user->mobile]]);
} }
} }

View File

@ -0,0 +1,408 @@
<?php
namespace app\command;
use app\admin\controller\AdminController;
use app\model\Admins;
use app\model\Blacks;
use app\model\Finances;
use app\model\Orders;
use app\server\SMS;
use app\server\Tongcheng;
use stdClass;
use support\Log;
use support\Redis;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* 同程数据拉取
*/
class SpiderTc extends Command
{
protected static $defaultName = 'spider:tc';
protected static $defaultDescription = '同程订单拉取器';
protected $_users = [];
protected function configure()
{
$this->setName('spider:tc')
->setDescription('同程订单拉取器')
->setDefinition(
new InputDefinition(array(
new InputOption('sn', 'sn', InputOption::VALUE_REQUIRED),
new InputOption('os', 'os', InputOption::VALUE_REQUIRED),
new InputOption('day', 'day', InputOption::VALUE_REQUIRED)
))
);
}
protected function users($routeType)
{
// if (count($this->_users) > 0) return $this->_users;
$users = Admins::where('status', 1)->where('is_order', 1)->where('route_type', $routeType)->select();
$us = [];
foreach ($users as $u) {
$ru = Redis::get('CRM:USER:ONLINE:' . $u->id);
if (empty($ru)) continue;
$_u = new stdClass();
$_u->username = $u->id;
$us[] = $_u;
};
$this->_users = $us;
return $this->_users;
}
protected $_redis_pool = [];
protected function poolUser($status = 0, $categoryDesc = '')
{
$routeType = AdminController::ROUTE_LISTS[mb_substr($categoryDesc, 0, 2)] ?? 10;
$status .= $routeType;
if (empty($this->_redis_pool[$status])) {
$this->_redis_pool[$status] = Redis::hGetAll('CRM:Pool:' . $status);
$users = $this->users($routeType);
$_users = [];
if (empty($this->_redis_pool[$status])) {
foreach ($users as $user) {
$_users[$user->username] = 0;
Redis::hSet('CRM:Pool:' . $status, $user->username, 0);
}
$this->_redis_pool[$status] = $_users;
} else {
asort($this->_redis_pool[$status]);
$key_users = array_keys($this->_redis_pool[$status]);
$username = $key_users[0];
$max = $this->_redis_pool[$status][$username];
$_users = [];
foreach ($users as $user) {
$_users[] = $user->username;
if (!in_array($user->username, $key_users)) {
$this->_redis_pool[$status][$username] = $max;
Redis::hSet('CRM:Pool:' . $status, $user->username, $max);
}
}
foreach ($this->_redis_pool[$status] as $username => $val) {
if (!in_array($username, $_users)) {
unset($this->_redis_pool[$status][$username]);
Redis::hDel('CRM:Pool:' . $status, $username);
}
}
}
}
$username = null;
try {
$pool = $this->_redis_pool[$status];
if (empty($pool)) $pool = [];
asort($pool);
$keys = array_keys($pool);
// if(!!config('app.debug', true)) return 1;
if (empty($keys)) {
Log::error(dirname(__FILE__) . __LINE__ . '没有可以分配的用户');
throw new \Exception('没有可以分配的用户');
}
$username = $keys[0];
$this->_redis_pool[$status][$username] += 1;
Redis::hIncrBy('CRM:Pool:' . $status, $username, 1);
} catch (\Exception $e) {
Log::error(dirname(__FILE__) . __LINE__ . '没有可以分配的用户', (array)json_encode($e));
throw new \Exception('没有可以分配的用户');
}
return $username;
}
/** @var InputInterface $output */
protected $output;
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
// $name = $input->getArgument('name');
$this->output = $output;
$output->writeln('START spider:tc:'.date('Y-m-d H:i:s'));
$os = $input->getOption('os');
$orderid = $input->getOption('sn');
$d = $input->getOption('day');
if (empty($d)) {
$d = 40;
}
if ($orderid) {
$this->reloadStatus($orderid, $os, $output);
return 1;
}
// echo "状态1\n";
// $this->poolUser(1);
// echo "状态2\n";
// $this->poolUser(2);
// echo "状态3\n";
// $this->poolUser(3);
// echo "状态4\n";
// $this->poolUser(4);
// echo "状态5\n";
// $this->poolUser(5);
// return 1;
// $this->checks($output);
// $output->writeln('CHECK spider:dy');
// $day = new Douyin();
// print_r($day->get(1));
// return 1;
// sleep(5);
// $time = strtotime(date('Y-m-d'));
// for ($i = 0; $i <= $d; $i++) {
// $day = $time - $i * 24 * 3600;
// $start = date('Y-m-d', $day);
// $end = date('Y-m-d 23:59:59', $day);
// $this->orders($start, $end, false);
// // $this->_kuaishouOrder($start, $end, false);
// }
// 8点20 开始分配订单
date_default_timezone_set('Asia/Shanghai');
$startTime = strtotime(date('Y-m-d 09:10'));
if (time() > $startTime) {
$start = date('Y-m-d 00:00:00', strtotime("-{$d} days"));
$output->writeln('END spider:tc:start:'.$start);
$end = date('Y-m-d 23:59:59');
$this->orders($start, $end, false);
}
if (date('H') >= 1 && date('H') <= 7) {
$this->reload($output);
}
$output->writeln('END spider:tc:'.date('Y-m-d H:i:s'));
return self::SUCCESS;
}
private function orders($start = null, $end = null, $order_id = false)
{
foreach (Orders::OSS as $k => $os) {
$pages = 10;
$page = 1;
while (true) {
if ($page > $pages) break;
$list = [];
switch ($k) {
case 6:
try {
$dou = new Tongcheng($k);
$list = $dou->get($page, $start, $end, '');
$pages = $dou->totalPage;
} catch (\Exception $e) {
Log::info('同程订单拉取失败:' . dirname(__FILE__) . __LINE__ . $e);
}
break;
default:
# code...
break;
}
foreach ($list as $order) {
$item = Orders::where('os', $order->os)->where('sn', $order->sn)->find();
if (empty($item)) {
$order->is_zhibo = 2;
$oldMobile = orders::where("os", $order->os)->where("mobile", $order->mobile)
->where("create_time", '>=', time() - 24 * 3600 * 3)->find();
if (!empty($oldMobile)) {
$admin_id = $oldMobile->admin_id;
} else {
// $admin_id = $this->poolUser($order->orderStatus, $order->category_desc);
try {
$admin_id = \app\server\Orders::poolUser($order->orderStatus, $order->product_id);
} catch (\Exception $exception) {
Log::info(sprintf('tc create order fail:%s, order_id:%s', $exception->getMessage(), $order->sn));
continue;
}
}
if (empty($admin_id)) return null;
$order->admin_id = $admin_id;
$order->give_time = time();
//判断是否需要发短信 200-待使用205-预约中抖音210-已预约抖音310-已履约抖音300-已完成
if (in_array($order->order_status, [200, 205, 210, 310, 300]) ) {
$this->sms($admin_id, $order);
}
$work = \app\server\Orders::getLiveRoomWork($order->create_at, $order->product_id);
if ($work) {
$order->live_room_work_id = $work->id;
}
//新获得一个用户的,提示管理员有新的订单
Redis::incrBy('CRM:USER:ONLINE:NEW:' . $admin_id, 1);
$item = new Orders();
}
if ($order->travel_date && empty($order->travel_end)) {
$days = $this->_days($order);
if ($days) {
$order->travel_end = date('Y-m-d 00:00:00', strtotime($order->travel_date) + $days * 24 * 3600);
}
}
if ($item->order_status !== 2 && $order->order_status == 2) {
Redis::incrBy('CRM:USER:WRITE:OFF:' . $item->admin_id, 1);
}
$back = $item->save($order->toArray());
if ($back) {
$this->_finance(0, $item->id, $item->asset_price);
}
}
// 数据小于50, 结束
if (empty($list)) {
break;
}
$page++;
}
}
}
private function reload($output)
{
$back = Redis::set('SpiderMt:reload:dy:lock', time(), 'EX', 3600 * 8, 'NX');
if (!$back) return;
$orders = Orders::where('create_at', '<=', (time() - 15 * 24 * 3600) * 1000)->where('os', 6)->wherein('status', [1, 2, 3])->select();
foreach ($orders as $order) {
$this->reloadStatus($order->sn, $order->os, $output);
}
}
/**
* @param $admin_id
* @param $order
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
private function sms($admin_id, $order) {
$user = Admins::cache(true)->where('id', $admin_id)->find();
$templateId = 261607;
if ((!config('app.debug', true) || config('app.debug', true) === 'false') && (time() * 1000 - $order->create_at) / 1000 < 2 * 24 * 3600) {
print_r([$order->mobile, $templateId, ['title' => $order->product_name, 'mobile' => $user->mobile]]);
$has = Blacks::where('mobile', $order->mobile)->find();
if (empty($has) && !empty($order->mobile)) {
SMS::juhe_sms_send($order->mobile, $templateId, ['title' => $order->product_name, 'mobile' => $user->mobile]);
} else {
sleep(10);
}
} else {
print_r([$order->mobile, $templateId, ['title' => $order->product_name, 'mobile' => $user->mobile]]);
}
}
private function reloadStatus($orderid, $os, $output)
{
$w[] = ['sn', '=', $orderid];
if ($os) $w[] = ['os', '=', $os];
$item = Orders::where($w)->find();
if (empty($item)) {
$output->writeln('没有找到订单');
}
$m = new Tongcheng($os);
$it = $m->get(1, null, null, $item->sn);
if ($it) {
$back = $item->save($it[0]);
if ($back) {
$this->_finance(0, $item->id, $item->asset_price);
}
} else {
$output->writeln('没有拉取到数据:' . $orderid);
}
}
private function _days($order)
{
if (stripos($order->product_name, '一日') !== false) {
return 1;
}
preg_match('/(\d)天/', $order->product_name, $all);
if (!empty($all) && intval($all[1]) > 0) {
return $all[1];
}
return 0;
}
private function zhibo($title, $os = 0)
{
if ($os == 3) {
return 2;
}
if (
strlen($title) - 3 == strripos($title, "")
|| strlen($title) - 1 == strripos($title, "!")
) {
return 1;
} elseif (strlen($title) - 3 == strripos($title, "")) {
return 2;
}
return 0;
}
private function _finance($type = 1, $order_id = 0, $price = 0)
{
// $fa = Finances::where('order_id', $order_id)->where('type', $type)->find();
$back = Redis::set('CRM:ORDER:LOCK:' . $order_id, time(), 'EX', 6, 'NX');
if (!$back) return;
$total = Finances::where('order_id', $order_id)->sum('total'); //总的关于这个订单的金额
//如果总金额大于提交上来的核销金额,那就是退费的
//如果提交上来的金额小于总金额,那就是核销的
if ($total > $price) {
$type = 2;
$fee = -($total - $price);
} elseif ($total < $price) {
$type = 1;
$fee = $price - $total;
} else {
return;
}
Finances::create([
'order_id' => $order_id,
'type' => $type,
'total' => $fee,
'status' => 1
]);
return;
// if(empty($fa)) {
// if($type == 2) {
// $has = Finances::where('order_id', $order_id)->where('type', 1)->find();
// if(empty($has)) return;
// }
// Finances::create([
// 'order_id' => $order_id,
// 'type' => $type,
// 'total' => $price,
// 'status' => 1
// ]);
// }
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace app\model;
use app\common\Keys;
use support\Redis;
class FilterMobiles extends base
{
/**
* @param string $mobile
* @param bool $refresh
* @return bool
* @throws \think\db\exception\BindParamException
*/
public static function isFilterMobile(string $mobile, bool $refresh = false): bool
{
$key = 'order_filter_mobiles';
if ($refresh || !Redis::exists($key)) {
$list = self::query()->column('id', 'mobile');
if (!empty($list)) {
$chunks = array_chunk($list, 50, true);
foreach($chunks as $mobiles) {
Redis::hMSet($key, $mobiles);
}
Redis::expire($key, 60*60*24);
}
}
return Redis::hExists($key, $mobile);
}
}

View File

@ -0,0 +1,6 @@
<?php
namespace app\model;
class LiveRoom extends base {
}

View File

@ -0,0 +1,90 @@
<?php
namespace app\model;
class LiveRoomWorks extends base {
public function zhubo() {
return $this->hasOne(Admins::class, 'id', 'zhubo_id')->field(['id', 'name', 'mobile']);
}
public function zhongkong() {
return $this->hasOne(Admins::class, 'id', 'zhongkong_id')->field(['id', 'name', 'mobile']);
}
/**
* @param $day
* @return LiveRoom[]|array|\think\Collection|\think\db\Query[]|\think\model\Collection
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function getDayWork($day, $liveRoomId) {
$end = date('Y-m-d', strtotime($day) + 24*60*60);
return self::where([
['start', '>', $day],
['end', '<', $end],
'live_room_id' => $liveRoomId,
])->with(['zhubo', 'zhongkong'])
->order('start', 'desc')
->select();
}
/**
* @param $id
* @param $roomId
* @param $start
* @param $end
* @return bool
*/
public static function dayWorkIsExists($id, $roomId, $start, $end) {
return self::where([
['start', '>', $start],
['end', '<', $end],
'live_room_id' => $roomId,
['id', '!=', $id]
])->find();
}
/**
* @param $id
* @param $zhuboId
* @param $day
* @return bool
*/
public static function zhuboWorkIsExists($id, $zhuboId, $day) {
return self::where([
'day' => $day,
'zhubo_id' => $zhuboId,
['id', '!=', $id]
])->find();
}
/**
* @param $day
* @return LiveRoomWorks[]|array|\think\Collection|\think\db\Query[]|\think\model\Collection
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function inWorkAdmin($day) {
return self::where(['day' => $day,])->field(['zhubo_id'])->select();
}
/**
* @param $type
* @param $adminId
* @param $start
* @param $end
*/
public static function staticOrder($type, $adminId, $start, $end) {
$field = $type == 3 ? 'zhongkong_id' : 'zhubo_id';
return self::where([
['start', '>', $start],
['end', '<', $end],
[$field, '=', $adminId]
])->fieldRaw('ifnull(sum(orders), 0) as orders')
->fieldRaw('ifnull(sum(total), 0) as total')
->fieldRaw('ifnull(sum(asset_total), 0) as asset_total')
->fieldRaw('ifnull(sum(TIMESTAMPDIFF(HOUR, `start`, `end`)), 0) as work_time')
->find();
}
}

View File

@ -16,18 +16,21 @@ class Orders extends base
const DouyinReservationStatus = [1 => '未预约', 2 => '待接单', 3 => '已预约', 4 => '已取消', 5 => '已完成', 6 => '取消/退款申请']; const DouyinReservationStatus = [1 => '未预约', 2 => '待接单', 3 => '已预约', 4 => '已取消', 5 => '已完成', 6 => '取消/退款申请'];
const DouyinStatus = [1 => '未核销', 2 => '已核销', 3 => '申请退款中', 4 => '已退款', 5 => '部分核销']; const DouyinStatus = [1 => '未核销', 2 => '已核销', 3 => '申请退款中', 4 => '已退款', 5 => '部分核销'];
// 订单状态(100 - 待付款150-用户取消200-待使用205-预约中抖音210-已预约抖音310-已履约抖音300-已完成400-已关闭)
const TongchengStatus = [100 => '待付款', 150 => '用户取消', 200 => '待使用', 205 => '预约中(抖音)', 210 => '已预约(抖音)', 310 => '已履约(抖音)', 300 => '已完成', 400 => '已关闭'];
const AllOssStatus = [1 => '待使用', 2 => '已核销', 3 => '已退款']; const AllOssStatus = [1 => '待使用', 2 => '已核销', 3 => '已退款'];
// 全部 未核销 已核销 已退款 未付款 已取消 待支付 申请退款中 部分核销 // 全部 未核销 已核销 已退款 未付款 已取消 待支付 申请退款中 部分核销
// 0 1 2 3 4 5 6 7 8 // 0 1 2 3 4 5 6 7 8
const AllOssStatusSql = [ const AllOssStatusSql = [
1 => '((os=1 and order_status=3) or (os=2 and order_status=4) or (os=3 and order_status=1))', //待使用 1 => '((os in (1,7) and order_status=3) or (os=2 and order_status=4) or (os in ("3", "5") and order_status=1))', //待使用
2 => '((os=1 and order_status=4) or (os=2 and order_status=5) or (os=3 and order_status=2))', //已核销 2 => '((os in (1,7) and order_status=4) or (os=2 and order_status=5) or (os in ("3", "5") and order_status=2))', //已核销
3 => '((os=1 and order_status=5) or (os=3 and order_status=4))' //已退款(快手暂无已退款状态) 3 => '((os in (1,7) and order_status=5) or (os in ("3", "5") and order_status=4))' //已退款(快手暂无已退款状态)
]; ];
const StatusName = ['待跟进', '跟进中', '已核销', '核销失败', '放弃跟单']; const StatusName = ['待跟进', '跟进中', '已核销', '核销失败', '放弃跟单'];
const OSS = [1 => '美团', 2 => '快手', 3 => '抖音', 4 => '全平台']; const OSS = [1 => '美团(甄选)', '7' => '美团(新国旅)', 2 => '快手', 3 => '抖音(甄选)', '5' => '抖音(新国旅)', '6' => '同程', 4 => '全平台'];
const timeType = ['create_time' => '添加记录时间', 'update_time' => '修改记录时间', 'last_follow' => '最后跟进时间', 'next_follow' => '下次跟进时间', 'travel_date' => '出行时间', 'create_at' => '下单时间']; const timeType = ['create_time' => '添加记录时间', 'update_time' => '修改记录时间', 'last_follow' => '最后跟进时间', 'next_follow' => '下次跟进时间', 'travel_date' => '出行时间', 'create_at' => '下单时间'];
@ -35,10 +38,14 @@ class Orders extends base
public function getOrderStatusNameAttr($val) public function getOrderStatusNameAttr($val)
{ {
if ($this->os == 1) if ($this->os == 1 || $this->os == 7)
return self::OrderStatus[$this->order_status] ?? '未知'; return self::OrderStatus[$this->order_status] ?? '未知';
elseif ($this->os == 3) elseif ($this->os == 3)
return self::DouyinStatus[$this->order_status] ?? '未知'; return self::DouyinStatus[$this->order_status] ?? '未知';
elseif ($this->os == 5)
return self::DouyinStatus[$this->order_status] ?? '未知';
elseif ($this->os == 6)
return self::TongchengStatus[$this->order_status] ?? '未知';
else else
return self::KuaishouStatus[$this->order_status] ?? '未知'; return self::KuaishouStatus[$this->order_status] ?? '未知';
} }

View File

@ -0,0 +1,5 @@
<?php
namespace app\model;
class Products extends base {
}

View File

@ -1,67 +0,0 @@
<?php
namespace app\server;
use app\model\ThirdMobileLogs;
use support\Log;
/**
* 阿里云市场相关接口
*/
class AliCloudApiService {
/**
* @param $mobile
* @return array|mixed
* @throws \think\db\exception\BindParamException
*/
public function getMobileArea($mobile) {
$host = "https://jumcvit.market.alicloudapi.com";
$path = "/mobile/area";
$method = "POST";
$appcode = "4402db67815049c18a55a996b5644f5b";
$headers = array();
array_push($headers, "Authorization:APPCODE " . $appcode);
//根据API的要求定义相对应的Content-Type
array_push($headers, "Content-Type".":"."application/x-www-form-urlencoded; charset=UTF-8");
$bodys = "mobile_number=" . $mobile;
$url = $host . $path;
$aliData = $this->curlRequest($url, $method, $headers, $bodys);
if (isset($aliData['code']) && $aliData['code'] == 200) {
ThirdMobileLogs::query()->insert([
'mobile' => $mobile,
'area' => $aliData['data']['area'],
'originalIsp' => $aliData['data']['originalIsp'],
]);
}
return $aliData['data'] ?? [];
}
/**
* @param $url
* @param $method
* @param $headers
* @param $bodys
* @return mixed
*/
private function curlRequest($url, $method, $headers, $bodys){
$curl = curl_init();
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_FAILONERROR, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
//设定返回信息中是否包含响应信息头启用时会将响应信息头作为数据流输出true 表示输出信息头, false表示不输出信息头
//如果想将响应结果json字符串转为json数组建议将 CURLOPT_HEADER 设置成 false
curl_setopt($curl, CURLOPT_HEADER, true);
if (1 == strpos("$".$url, "https://"))
{
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
}
curl_setopt($curl, CURLOPT_POSTFIELDS, $bodys);
curl_close($curl);
list($header, $body) = explode("\r\n\r\n", curl_exec($curl), 2);
return json_decode($body, true);
}
}

View File

@ -15,16 +15,19 @@ class Douyin
private $cookie = ''; private $cookie = '';
private $id = ''; private $id = '';
private $os = '';
private $token = ''; private $token = '';
private $tryTimes = 0;
private $gate = 'https://life.douyin.com'; private $gate = 'https://life.douyin.com';
public $totalPage = 6; public $totalPage = 6;
private $keywords = ['泰国', '普吉岛', '西藏', 'S', 'G', 's', 'g']; private $keywords = ['泰国', '普吉岛', '西藏', 'S', 'G', 's', 'g'];
public function __construct() public function __construct($os = 3)
{ {
$this->cookie = $this->_cookie(); $this->cookie = $this->_cookie();
$this->id = $this->_id(); $this->id = $this->_id();
$this->token = $this->_token(); $this->os = $os;
$this->token = $this->_token('', $os);
} }
public function login() public function login()
@ -86,7 +89,6 @@ class Douyin
if ($list && $list->status_code == 0 && !empty($list->order_list)) { if ($list && $list->status_code == 0 && !empty($list->order_list)) {
foreach ($list->order_list as $order) { foreach ($list->order_list as $order) {
echo "抖音 订单号:{$order->order_info->order_id} \n\n";
if (empty($cert)) { if (empty($cert)) {
$cert = $this->_cert($order->order_info->order_id); $cert = $this->_cert($order->order_info->order_id);
if (empty($cert)) { if (empty($cert)) {
@ -160,18 +162,19 @@ class Douyin
$this->totalPage = 1; $this->totalPage = 1;
return $_list; return $_list;
} }
echo date('Y-m-d', $start) . '--' . date('Y-m-d', $end) . " 抖音 page:{$page} total_count:" . $list->data->pagination->total_count . " page_count:" . $list->data->pagination->page_count . " \n\n"; Log::info(date('Y-m-d', $start) . '--' . date('Y-m-d', $end) . " 抖音 page:{$page} total_count:" . $list->data->pagination->total_count . " page_count:" . $list->data->pagination->page_count );
foreach ($list->data->list as $order) { foreach ($list->data->list as $order) {
echo "抖音 订单号:{$order->order_id} \n\n"; Log::info("抖音 订单号:{$order->order_id} \n\n");
$orderDetail = $this->_orderDetail($order->order_id, $order->status); $orderDetail = $this->_orderDetail($order->order_id, $order->status);
if (empty($orderDetail)) { if (empty($orderDetail)) {
Log::info("抖音 订单详情拉取失败:{$order->order_id}");
throw new Exception("抖音拉单{$order->order_id}详情获取失败"); throw new Exception("抖音拉单{$order->order_id}详情获取失败");
} }
$item = new Orders(); $item = new Orders();
$item->os = 3; $item->os = $this->os;
$item->sn = $order->order_id; $item->sn = $order->order_id;
$item->product_id = $order->sku->sku_id; $item->product_id = $order->sku->sku_id;
$item->product_name = $order->sku->title; $item->product_name = $order->sku->title;
@ -187,8 +190,8 @@ class Douyin
$item->asset_status = $order->status ?? 0; $item->asset_status = $order->status ?? 0;
$item->category_desc = $order->sku->title; $item->category_desc = $order->sku->title;
$item->asset_price = $order->amount->verify_amount ?? 0; $item->asset_price = $order->amount->verify_amount ?? 0;
$item->is_refunded = $item->order_status == 4 ? 1 : 0; // 是否已退款1-已退款0-未退款)
//抖音的核销金额需要查核销订单 //抖音的核销金额需要查核销订单
if ($item->create_at < strtotime('2024-05-22') * 1000) { if ($item->create_at < strtotime('2024-05-22') * 1000) {
$this->totalPage = 1; $this->totalPage = 1;
break; break;
@ -211,7 +214,7 @@ class Douyin
} }
} }
if (!$kw_match) { if ($this->os == 3 && !$kw_match) {
printf("skip order: %s %s\n", $item->sn, $item->product_name); printf("skip order: %s %s\n", $item->sn, $item->product_name);
continue; continue;
} }
@ -280,64 +283,10 @@ class Douyin
return true; return true;
} }
/*public function _killMiddleProcesses($pattern, $maxProcesses)
{
// 检查操作系统是否为 Linux
if (PHP_OS !== 'Linux') {
echo "当前系统不是 Linux跳过执行。\n";
return false;
}
// 获取当前进程的 PID
$currentPid = getmypid();
// 获取进程列表
$command = "ps aux | grep '$pattern' | grep -v grep | grep -v '$currentPid' | awk '{print $2,$10}'";
$output = [];
exec($command, $output);
$processCount = count($output);
// 如果进程数量超过最大值,则终止中间部分的 x-12 个进程
if ($processCount > $maxProcesses) {
$processesToKill = $processCount - $maxProcesses;
$processesKilled = 0;
// 找到运行时间最短的不包含自己的进程并终止它
foreach ($output as $line) {
list($pid, $runtime) = explode(' ', $line);
$runtimeParts = explode(':', $runtime);
$hours = intval($runtimeParts[0]);
$minutes = intval($runtimeParts[1]);
$totalMinutes = $hours * 60 + $minutes;
if ($pid !== $currentPid) {
exec("kill -9 $pid");
$processesKilled++;
echo "Killed process with PID: $pid\n";
if ($processesKilled >= $processesToKill) {
break;
}
}
}
if ($processesKilled === 0) {
echo "No processes to kill.\n";
}
} else {
echo "Process count is not over the limit.\n";
}
return true;
}*/
public function _killMiddleProcesses($pattern, $maxProcesses) public function _killMiddleProcesses($pattern, $maxProcesses)
{ {
// 检查操作系统是否为 Linux // 检查操作系统是否为 Linux
if (PHP_OS !== 'Linux') { if (PHP_OS !== 'Linux') {
echo "当前系统不是 Linux跳过执行。\n";
return false; return false;
} }
@ -388,14 +337,14 @@ class Douyin
exec("kill -9 {$process['pid']}"); exec("kill -9 {$process['pid']}");
$processesKilled++; $processesKilled++;
echo "Killed process with PID: {$process['pid']}\n"; Log::info("Killed process with PID: {$process['pid']}\n");
} }
if ($processesKilled === 0) { if ($processesKilled === 0) {
echo "No processes to kill.\n"; Log::info( "No processes to kill.\n");
} }
} else { } else {
echo "Process count is not over the limit.\n"; Log::info( "Process count is not over the limit.\n");
} }
return true; return true;
@ -408,7 +357,7 @@ class Douyin
'order_id' => $orderId, 'order_id' => $orderId,
'book_order_id' => '', 'book_order_id' => '',
'status' => $status, 'status' => $status,
'root_life_account_id' => $this->_id() 'root_life_account_id' => $this->_id($this->os)
]); ]);
return $orderDetail->data ?? null; return $orderDetail->data ?? null;
} }
@ -486,22 +435,31 @@ class Douyin
public function _cookie($data = '') public function _cookie($data = '')
{ {
if ($data) Redis::set('Douyin:cookie', $data); $key = 'Douyin:cookie';
return Redis::get('Douyin:cookie'); if ($this->os == 5) {
$key = 'Douyin:cookie-' . $this->os;
}
if ($data) Redis::set($key, $data);
return Redis::get($key);
} }
public function _id($id = '') public function _id($id = '')
{ {
if ($id) Redis::set('Douyin:id', $id); if ($this->os == 5) {
return Redis::get('Douyin:id'); return '7399206501845403686';
} else {
// return '7259680722519066679';
return Redis::get('Douyin:id');
}
} }
protected $_try = 0; protected $_try = 0;
public function _token($token = '') public function _token($token = '')
{ {
if ($token) Redis::set('Douyin:token', $token, 'ex', 3600 * 24 - 50); $dyKey = sprintf('Douyin:token-%s', $this->os);
$token = Redis::get('Douyin:token'); if ($token) Redis::set($dyKey, $token, 'ex', 3600 * 24 - 50);
$token = Redis::get($dyKey);
if (empty($token) && $this->_cookie()) { if (empty($token) && $this->_cookie()) {
if ($this->_try > 3) { if ($this->_try > 3) {
throw new Exception("cookie 失效"); throw new Exception("cookie 失效");
@ -509,10 +467,10 @@ class Douyin
$id = $this->_curl('/life/gate/v1/user/detail', null, 'HEAD'); $id = $this->_curl('/life/gate/v1/user/detail', null, 'HEAD');
$this->_try++; $this->_try++;
if ($id) { if ($id) {
Redis::set('Douyin:token', $id, 'ex', 3600 * 24 - 50); Redis::set($dyKey, $id, 'ex', 3600 * 24 - 50);
} }
} }
return Redis::get('Douyin:token'); return Redis::get($dyKey);
} }
public function _curl($url, $params, $method = 'GET') public function _curl($url, $params, $method = 'GET')
@ -523,6 +481,7 @@ class Douyin
if ($requestCount == 1) { if ($requestCount == 1) {
Redis::expire($key, 604800); // 7天 Redis::expire($key, 604800); // 7天
} }
$cookiePath = sprintf(runtime_path() . '%s_dy_cookie.txt', $this->os);
$http = $this->gate . $url; $http = $this->gate . $url;
if ($method == 'GET') { if ($method == 'GET') {
@ -540,21 +499,22 @@ class Douyin
'Content-type: application/json' 'Content-type: application/json'
]; ];
if ($method == 'GET' || $method == 'POST') { if ($method == 'GET' || $method == 'POST') {
$header[] = 'x-secsdk-csrf-token: ' . $this->_token(); $header[] = 'x-secsdk-csrf-token: ' . $this->_token('', $this->os);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
} else { } else {
curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookiePath);
$header[] = 'x-secsdk-csrf-request: 1'; $header[] = 'x-secsdk-csrf-request: 1';
} }
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_COOKIE, $this->_cookie()); curl_setopt($ch, CURLOPT_COOKIE, $this->_cookie(''));
curl_setopt($ch, CURLOPT_HTTPHEADER, $header); curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookiePath);
if ($method == 'POST') { if ($method == 'POST') {
curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params)); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
} }
$body = curl_exec($ch); $body = curl_exec($ch);
curl_close($ch); curl_close($ch);
if (!in_array($method, ['GET', 'POST'])) { if (!in_array($method, ['GET', 'POST'])) {
@ -569,6 +529,16 @@ class Douyin
} }
return null; return null;
} }
return json_decode($body);
$res = json_decode($body);
if (!isset($res->status_code) || $res->status_code != 0) {
Log::info("抖音 os{$this->os} \n\n");
Log::info( "抖音 body{$body} \n\n");
if ($this->tryTimes > 1) {
(new ThirdApiService())->weComNotice($this->os);
}
$this->tryTimes++;
}
return $res;
} }
} }

View File

@ -10,9 +10,12 @@ class Meituan {
private $cookie = ''; private $cookie = '';
private $gate = 'https://lvyou.meituan.com'; private $gate = 'https://lvyou.meituan.com';
public $totalPage = 6; public $totalPage = 6;
public $os;
private $tryTimes = 0;
public function __construct() public function __construct($os = 1)
{ {
$this->os = $os;
$this->cookie = $this->_cookie(); $this->cookie = $this->_cookie();
} }
@ -51,7 +54,7 @@ class Meituan {
$params = [ $params = [
'page' => $page, 'page' => $page,
'count' => 20, 'count' => 50,
'orderId' => $orderId, 'orderId' => $orderId,
'productId' => '', 'productId' => '',
'orderTimeTo' => $end ?? '', 'orderTimeTo' => $end ?? '',
@ -74,21 +77,21 @@ class Meituan {
} }
} }
if($list && $list->code == 0) {
$this->totalPage = $list->data->totalPage;
}
$_list = []; $_list = [];
if($list && $list->code == 0 && $list->data && is_array($list->data->orderList)) { if($list && $list->code == 0 && $list->data && is_array($list->data->orderList)) {
foreach($list->data->orderList as $order) { foreach($list->data->orderList as $order) {
echo "美团 订单号:{$order->orderId} \n\n"; Log::info("美团 订单号:{$order->orderId} \n\n");
if(in_array($order->orderId, ['',''])) { if(in_array($order->orderId, ['',''])) {
echo "异常订单:". $order->orderId; Log::info( "异常订单:". $order->orderId);
sleep(100); sleep(100);
} }
// 过滤未付款订单
if ($order->orderStatus == 1) {
continue;
}
$item = new Orders(); $item = new Orders();
$item->os = 1; $item->os = $this->os;
$item->sn = $order->orderId; $item->sn = $order->orderId;
$item->product_id = $order->productId; $item->product_id = $order->productId;
$item->product_name = $order->productName; $item->product_name = $order->productName;
@ -103,7 +106,9 @@ class Meituan {
$item->order_status = $order->orderStatus; $item->order_status = $order->orderStatus;
$item->refund_status = $order->refundStatus; $item->refund_status = $order->refundStatus;
$item->category_desc = $order->categoryDesc; $item->category_desc = $order->categoryDesc;
$item->is_zhibo = $order->bizOrderChannel == 5 ? 1 : 0; // 是否直播5-视频号3-小程序)
$item->asset_price = $order->orderStatus == 4 ? $order->totalPrice: 0; //已核销订单,核销金额为订单金额 $item->asset_price = $order->orderStatus == 4 ? $order->totalPrice: 0; //已核销订单,核销金额为订单金额
$item->is_refunded = $item->order_status == 5 ? 1 : 0; // 是否已退款1-已退款0-未退款)
$_list[] = $item; $_list[] = $item;
} }
@ -113,8 +118,9 @@ class Meituan {
} }
public function _cookie($data = '') { public function _cookie($data = '') {
if($data) Redis::set('Meituan:cookie', $data); $key = sprintf('Meituan:cookie-%s', $this->os);
return Redis::get('Meituan:cookie'); if($data) Redis::set($key, $data);
return Redis::get($key);
} }
public function _curl($url, $params, $method = 'GET') { public function _curl($url, $params, $method = 'GET') {
@ -149,8 +155,19 @@ class Meituan {
curl_setopt($ch, CURLOPT_POSTFIELDS, $params); curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
} }
echo $body = curl_exec($ch); $body = curl_exec($ch);
curl_close($ch); curl_close($ch);
return json_decode($body); $res = json_decode($body);
if (!isset($res->code) || $res->code != 0) {
Log::info('os:' . $this->os);
Log::info('http:' . $http);
Log::info('body:' . $body);
if ($this->tryTimes > 1) {
(new ThirdApiService())->weComNotice($this->os);
}
$this->tryTimes++;
}
return $res;
} }
} }

View File

@ -3,20 +3,27 @@
namespace app\server; namespace app\server;
use app\common\Error; use app\common\Error;
use app\model\Admins;
use app\model\Admins as AdminsModel; use app\model\Admins as AdminsModel;
use app\model\Blacks;
use app\model\FilterMobiles;
use app\model\Finances as FinancesModel; use app\model\Finances as FinancesModel;
use app\model\LiveRoomWorks;
use app\model\Orders as OrdersModel; use app\model\Orders as OrdersModel;
use app\model\Products;
use support\Log; use support\Log;
use support\Redis; use support\Redis;
class Orders { class Orders {
protected static $redisPool = [];
public static function isDaishiyong(OrdersModel $order): bool public static function isDaishiyong(OrdersModel $order): bool
{ {
// 根据 OrdersModel::AllOssStatusSql[1] 进行判断 // 根据 OrdersModel::AllOssStatusSql[1] 进行判断
// ((os=1 and order_status=3) or (os=2 and order_status=4) or (os=3 and order_status=1)) // ((os=1 and order_status=3) or (os=2 and order_status=4) or (os=3 and order_status=1))
return $order->os == 1 && $order->order_status == 3 return (in_array($order->os, [1, 7]) && $order->order_status == 3)
|| $order->os == 2 && $order->order_status == 4 || ($order->os == 2 && $order->order_status == 4)
|| $order->os == 3 && $order->order_status == 1; || (in_array($order->os, [3, 5]) && in_array($order->order_status, [1, 5]))
|| (in_array($order->os, [6]) && in_array($order->order_status, [200]));
} }
/** /**
@ -42,6 +49,7 @@ class Orders {
$ttl = 86400; $ttl = 86400;
$result = []; $result = [];
$admins = array_column($admins, null, 'id');
foreach($orders as $order) { foreach($orders as $order) {
$admin_mobile = $admins[$order->admin_id] ?? ''; $admin_mobile = $admins[$order->admin_id] ?? '';
@ -49,6 +57,11 @@ class Orders {
$result[$order->sn] = Error::undefined('client mobile or admin mobile invalid'); $result[$order->sn] = Error::undefined('client mobile or admin mobile invalid');
continue; continue;
} }
if (FilterMobiles::isFilterMobile($order->mobile)) {
$result[$order->sn] = Error::undefined('刷单账单');
continue;
}
$admin_mobile = $admin_mobile['mobile'] ?? '';
if (Blacks::CExists($order->mobile)) { if (Blacks::CExists($order->mobile)) {
$result[$order->sn] = Error::ERR_SMS_BLACKS; $result[$order->sn] = Error::ERR_SMS_BLACKS;
@ -85,7 +98,8 @@ class Orders {
switch ($order->os) { switch ($order->os) {
case 1: case 1:
$mt = new Meituan(); case 7:
$mt = new Meituan($order->os);
$it = $mt->get(1, null, null, $order->sn); $it = $mt->get(1, null, null, $order->sn);
if ($it) { if ($it) {
@ -94,13 +108,14 @@ class Orders {
break; break;
case 3: case 3:
$dy = new Douyin(); case 5:
$dy = new Douyin($order->os);
$it = $dy->get(1, null, null, $order->sn); $it = $dy->get(1, null, null, $order->sn);
if ($it) { if ($it) {
$got = $it[0]; $got = $it[0];
// 查询未预约状态 // 查询未预约状态
if ($order->appointment_status != 1) { if ($order->appointment_status == 0) {
$appointment_status = $dy->_orderMakeAppointmentStatus($order->sn); $appointment_status = $dy->_orderMakeAppointmentStatus($order->sn);
if ($appointment_status !== null) { if ($appointment_status !== null) {
$got['appointment_status'] = 1; $got['appointment_status'] = 1;
@ -108,6 +123,14 @@ class Orders {
} }
} }
break;
case 6:
$tc = new Tongcheng($order->os);
$it = $tc->get(1, null, null, $order->sn);
if ($it) {
$got = $it[0];
}
break; break;
} }
@ -165,7 +188,8 @@ class Orders {
$got = null; $got = null;
switch ($order->os) { switch ($order->os) {
case 1: case 1:
$mt = new Meituan(); case 7:
$mt = new Meituan($order->os);
$it = $mt->get(1, null, null, $order->sn); $it = $mt->get(1, null, null, $order->sn);
if ($it) { if ($it) {
@ -174,7 +198,8 @@ class Orders {
break; break;
case 3: case 3:
$dy = new Douyin(); case 5:
$dy = new Douyin($order->os);
$it = $dy->get(1, null, null, $order->sn); $it = $dy->get(1, null, null, $order->sn);
if ($it) { if ($it) {
$got = $it[0]; $got = $it[0];
@ -196,4 +221,134 @@ class Orders {
return $order; return $order;
} }
/**
* @param int $orderCreateTime
* @param $productId
* @return array|mixed|\think\Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function getLiveRoomWork(int $orderCreateTime, $productId) {
$orderCreateDate = date('Y-m-d H:i:s', $orderCreateTime/1000);
$roomWork = LiveRoomWorks::join('live_room', 'live_room_works.live_room_id=live_room.id')
->where('live_room_works.start', '<', $orderCreateDate)
->where('live_room_works.end', '>', $orderCreateDate)
->whereRaw(sprintf('find_in_set(%s, live_room.`product_ids`)', $productId))
->field(['live_room_works.id'])
->find();
return $roomWork;
}
/**
* 分配用户
* @param int $status
* @param string $categoryDesc
* @return int|string
* @throws \Exception
*/
public static function poolUser($status, $thirdProductId) {
$status .= $thirdProductId;
if (empty(self::$redisPool[$status])) {
self::$redisPool[$status] = Redis::hGetAll('CRM:Pool:' . $status);
$users = self::users($thirdProductId);
shuffle($users);
$_users = [];
if (empty(self::$redisPool[$status])) {
foreach ($users as $user) {
$_users[$user->username] = 0;
Redis::hSet('CRM:Pool:' . $status, $user->username, 0);
}
self::$redisPool[$status] = $_users;
} else {
asort(self::$redisPool[$status]);
$key_users = array_keys(self::$redisPool[$status]);
$username = $key_users[0];
$max = self::$redisPool[$status][$username];
$_users = [];
foreach ($users as $user) {
$_users[] = $user->username;
if (!in_array($user->username, $key_users)) {
self::$redisPool[$status][$username] = $max;
Redis::hSet('CRM:Pool:' . $status, $user->username, $max);
}
}
foreach (self::$redisPool[$status] as $username => $val) {
if (!in_array($username, $_users)) {
unset(self::$redisPool[$status][$username]);
Redis::hDel('CRM:Pool:' . $status, $username);
}
}
}
}
$username = null;
try {
$pool = self::$redisPool[$status];
if (empty($pool)) $pool = [];
asort($pool);
$keys = array_keys($pool);
if (empty($keys)) {
throw new \Exception('没有可以分配的用户');
}
$username = $keys[0];
self::$redisPool[$status][$username] += 1;
Redis::hIncrBy('CRM:Pool:' . $status, $username, 1);
} catch (\Exception $e) {
Log::error(dirname(__FILE__) . __LINE__ . '没有可以分配的用户', func_get_args());
throw new \Exception('没有可以分配的用户');
}
return $username;
}
/**
* @param $thirdProductId
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
protected static function users($thirdProductId) {
$product = Products::query()->where('third_product_id', $thirdProductId)->find();
if (empty($product)) {
return [];
}
$users = Admins::where('status', 1)->where('is_order', 1)->whereFindInSet('product_ids', $product->id)->select();
$us = [];
foreach ($users as $u) {
$ru = Redis::get('CRM:USER:ONLINE:' . $u->id);
if (empty($ru)) continue;
$_u = new \stdClass();
$_u->username = $u->id;
$us[] = $_u;
};
return $us;
}
/**
* 发送订单短信
* @param $admin_id
* @param $order
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function sendOrderSms($admin_id, $order)
{
$user = Admins::cache(true)->where('id', $admin_id)->find();
if ((!config('app.debug', true) || config('app.debug', true) === 'false') && (time() * 1000 - $order->create_at) / 1000 < 2 * 24 * 3600) {
$has = Blacks::where('mobile', $order->mobile)->find();
if (empty($has) && !empty($order->mobile)) {
SMS::juhe_sms_send($order->mobile, 261607, ['title' => $order->product_name, 'mobile' => $user->mobile]);
} else {
sleep(10);
}
} else {
Log::info('订单未发送短息:' . json_encode([$order->mobile, 261607, ['title' => $order->product_name, 'mobile' => $user->mobile]]));
}
}
} }

View File

@ -0,0 +1,131 @@
<?php
namespace app\server;
use app\model\Orders;
use app\model\ThirdMobileLogs;
use http\Client;
use support\Log;
use support\Redis;
/**
* 阿里云市场相关接口
*/
class ThirdApiService {
private $notice = '%s token已过期请及时更换';
/**
* @param $mobile
* @return array|mixed
* @throws \think\db\exception\BindParamException
*/
public function getMobileArea($mobile) {
$host = "https://jumcvit.market.alicloudapi.com";
$path = "/mobile/area";
$method = "POST";
$appcode = "4402db67815049c18a55a996b5644f5b";
$headers = array();
array_push($headers, "Authorization:APPCODE " . $appcode);
//根据API的要求定义相对应的Content-Type
array_push($headers, "Content-Type".":"."application/x-www-form-urlencoded; charset=UTF-8");
$bodys = "mobile_number=" . $mobile;
$url = $host . $path;
$aliData = $this->curlMobileRequest($url, $method, $headers, $bodys);
if (isset($aliData['code']) && $aliData['code'] == 200) {
ThirdMobileLogs::query()->insert([
'mobile' => $mobile,
'area' => $aliData['data']['area'],
'originalIsp' => $aliData['data']['originalIsp'],
]);
}
return $aliData['data'] ?? [];
}
/**
* @param $url
* @param $method
* @param $headers
* @param $bodys
* @return mixed
*/
private function curlMobileRequest($url, $method, $headers, $bodys){
$curl = curl_init();
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_FAILONERROR, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
//设定返回信息中是否包含响应信息头启用时会将响应信息头作为数据流输出true 表示输出信息头, false表示不输出信息头
//如果想将响应结果json字符串转为json数组建议将 CURLOPT_HEADER 设置成 false
curl_setopt($curl, CURLOPT_HEADER, true);
if (1 == strpos("$".$url, "https://"))
{
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
}
curl_setopt($curl, CURLOPT_POSTFIELDS, $bodys);
curl_close($curl);
list($header, $body) = explode("\r\n\r\n", curl_exec($curl), 2);
return json_decode($body, true);
}
/**
* 发送企微提醒
* @param $message
* @return mixed
*/
public function weComNotice($os) {
$url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=e138b55c-2886-4729-922e-9a8c753f75ee';
$method = 'POST';
$message = sprintf($this->notice, Orders::OSS[$os] ?? '');
$bodys = [
'msgtype' => 'text',
'text' => [
'content' => $message
]
];
$key = 'notice-' . $os;
if (Redis::setNx($key, 1)) {
Redis::expire($key, 600); // 十分钟内通知一次
$this->curlRequest($url, $method, [], $bodys);
}
return true;
}
/**
* @param $url
* @param $method
* @param $headers
* @param $bodys
* @return mixed
*/
private function curlRequest($url, $method, $headers, $bodys){
$curl = curl_init();
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_FAILONERROR, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
//设定返回信息中是否包含响应信息头启用时会将响应信息头作为数据流输出true 表示输出信息头, false表示不输出信息头
//如果想将响应结果json字符串转为json数组建议将 CURLOPT_HEADER 设置成 false
curl_setopt($curl, CURLOPT_HEADER, true);
if (1 == strpos("$".$url, "https://"))
{
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
}
if (is_array($bodys)) {
$bodys = json_encode($bodys);
}
curl_setopt($curl, CURLOPT_POSTFIELDS, $bodys);
curl_setopt($curl, CURLOPT_HTTPHEADER, array_merge($headers, [
'Content-Type: application/json',
'Content-Length: ' . strlen($bodys)
]));
curl_close($curl);
list($header, $body) = explode("\r\n\r\n", curl_exec($curl), 2);
Log::info(sprintf('third req:%s, res:%s', $url, $body));
return json_decode($body, true);
}
}

View File

@ -0,0 +1,283 @@
<?php
namespace app\server;
use app\model\Orders;
use Exception;
use support\Log;
use support\Redis;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
/**
* 同程订单处理
*/
class Tongcheng {
private $os = '';
protected $cookie;
private $gate = 'https://ebk.17u.cn';
public $totalPage = 6;
private $tryTimes = 0;
public function __construct($os = 6)
{
$this->cookie = $this->_cookie();
$this->os = $os;
}
/**
* @throws ModelNotFoundException
* @throws DataNotFoundException
* @throws DbException
* @throws Exception
*/
public function get($page, $start = null, $end = null, $orderId = '')
{
if (empty($start) || empty($end)) {
$start = date('Y-m-d 00:00:00');
$end = date('Y-m-d 23:59:59');
}
if ($orderId) {
$start = $end = null;
}
$_list = [];
$list = $this->_certificateList($start, $end, $page, $orderId);
if (empty($list->data->list) || $list->code != 1000) {
$this->totalPage = 1;
return $_list;
}
Log::info($start . '--' . $end . " 同程 page:{$page} total_count:" . $list->data->totalCount . " page_count:" . $list->data->totalPage);
foreach ($list->data->list as $order) {
Log::info("同程 订单号:{$order->orderId} \n\n");
// $orderDetail = $this->_orderDetail($order->order_id, $order->status);
// if (empty($orderDetail)) {
// Log::info("同程 订单详情拉取失败:{$order->order_id}");
// throw new Exception("同程拉单{$order->order_id}详情获取失败");
// }
$order->orderAmount = $order->orderAmount * 100;
$item = new Orders();
$item->os = $this->os;
$item->sn = $order->orderId;
$item->product_id = $order->lineId;
$item->product_name = $order->title;
$item->category_id = $order->agentFinderId;
$item->category_desc = $order->agentFinderNickname;
$item->create_at = strtotime($order->createTime) * 1000;
$item->mobile = $order->mobile;
$item->travel_date = null;
$item->total_price = $order->orderAmount;
$item->quantity = $order->productCount;
$item->unit_price = $order->orderAmount/$order->productCount;
$item->actual_price = $order->orderAmount;
$item->order_status = $order->orderStatus; // 订单状态(100 - 待付款150-用户取消200-待使用205-预约中抖音210-已预约抖音310-已履约抖音300-已完成400-已关闭)
$item->appointment_status = in_array($order->orderStatus, [210, 310, 300]) ? 1 : 0; // 是否已预约
$item->asset_status = $order->status ?? 0; // 核销状态
$item->asset_price = $order->orderStatus == 300 ? $order->orderAmount : 0; // 核销金额
//抖音的核销金额需要查核销订单
$_list[] = $item;
}
return $_list; //返回半成品
}
/**
* @throws Exception
*/
public function _certificateList($start, $end, $page, $orderId)
{
$params = [
'begin' => $start,// 开始时间,格式2024-09-01 00:00:00
'end' => $end,// 结束时间 格式2024-09-01 00:00:00
'orderStatus' => "",// 订单状态
'current' => $page, // 当前数量
'size' => 50, // 显示数量
'orderId' => $orderId, // 订单号
];
$this->_getRetriesLock();
$list = $this->_curl("/merchant/api/market-live/wxVideoV2/supplierOrderByPage", $params, 'POST');
if (empty($list) || $list->code != 1000) {
$this->_getRetriesLock();
$list = $this->_curl("/merchant/api/market-live/wxVideoV2/supplierOrderByPage", $params, 'POST');
if (empty($list) || $list->code != 1000) {
Log::error('===查询时间:' . $start . '--' . $end . '====certificate_list: ' . json_encode($list));
throw new Exception("同程拉单失败,Err:" . json_encode($list));
}
}
return $list;
}
public function _getRetriesLock(): bool
{
$pattern = 'php webman spider:tc';
$maxProcesses = 8;
$this->_killMiddleProcesses($pattern, $maxProcesses);
$maxRetries = 5;
$retries = 0;
while ($retries < $maxRetries) {
$back = Redis::set('SpiderTc:CertificateList:lock', time(), 'EX', 1, 'NX');
if ($back) {
break;
}
$retries++;
sleep(1);
}
return true;
}
public function _killMiddleProcesses($pattern, $maxProcesses)
{
// 检查操作系统是否为 Linux
if (PHP_OS !== 'Linux') {
return false;
}
// 获取当前进程的 PID
$currentPid = getmypid();
// 获取进程列表
$command = "ps aux | grep '$pattern' | grep -v grep | grep -v '$currentPid' | awk '{print $2,$10}'";
$output = [];
exec($command, $output);
$processList = [];
foreach ($output as $line) {
list($pid, $runtime) = explode(' ', $line);
$runtimeParts = explode(':', $runtime);
$hours = intval($runtimeParts[0]);
$minutes = intval($runtimeParts[1]);
$totalMinutes = $hours * 60 + $minutes;
$processList[] = ['pid' => $pid, 'runtime' => $totalMinutes];
}
// 按运行时间排序
usort($processList, function ($a, $b) {
return $a['runtime'] <=> $b['runtime'];
});
// 过滤掉当前 PID 和运行时间最短的 PID
if (!empty($processList)) {
array_shift($processList); // 移除运行时间最短的
}
$processList = array_filter($processList, function ($process) use ($currentPid) {
return $process['pid'] != $currentPid;
});
$processCount = count($processList);
// 如果进程数量超过最大值,则终止中间部分的进程
if ($processCount > $maxProcesses) {
$processesToKill = $processCount - $maxProcesses;
$processesKilled = 0;
// 杀死运行时间最短的进程
foreach ($processList as $process) {
if ($processesKilled >= $processesToKill) {
break;
}
exec("kill -9 {$process['pid']}");
$processesKilled++;
Log::info("Killed process with PID: {$process['pid']}\n");
}
if ($processesKilled === 0) {
Log::info("No processes to kill.\n");
}
} else {
Log::info("Process count is not over the limit.\n");
}
return true;
}
public function _orderDetail($orderId = null, $status = null)
{
$orderDetail = $this->_curl("/life/trip/v2/travel_agency/book/detail", [
'order_id' => $orderId,
'book_order_id' => '',
'status' => $status,
]);
return $orderDetail->data ?? null;
}
public function _cookie($data = '')
{
$key = 'Tongcheng:cookie-' . $this->os;
if ($data) Redis::set($key, $data);
return Redis::get($key);
}
public function _curl($url, $params, $method = 'GET')
{
// 请求次数统计
$key = sprintf("Tongcheng:ReqiestCount:%s", date('Y-m-d'));
$requestCount = Redis::incrBy($key, 1);
if ($requestCount == 1) {
Redis::expire($key, 604800); // 7天
}
$cookiePath = sprintf(runtime_path() . '%s_tc_cookie.txt', $this->os);
$http = $this->gate . $url;
if ($method == 'GET') {
$http = $http . '?' . http_build_query($params);
}
$ch = curl_init($http);
$header = [
'Content-type: application/json'
];
if ($method == 'GET' || $method == 'POST') {
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
} else {
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookiePath);
}
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_COOKIE, $this->_cookie());
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookiePath);
if ($method == 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
}
$body = curl_exec($ch);
curl_close($ch);
if (!in_array($method, ['GET', 'POST'])) {
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$header = substr($body, 0, $header_size);
$in = explode("\n", $body);
foreach ($in as $i) {
if (stripos($i, 'x-ware-csrf-token') !== false) {
$inn = explode(',', $i);
return $inn[1];
}
}
return null;
}
$res = json_decode($body);
if (!isset($res->code) || $res->code != 1000) {
Log::info( "同城 body{$body} \n\n");
if ($this->tryTimes > 1) {
(new ThirdApiService())->weComNotice($this->os);
}
$this->tryTimes++;
}
return $res;
}
}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
.app-container[data-v-45d18cae]{position:relative;padding-bottom:60px}.el-table[data-v-45d18cae],.filter-container[data-v-45d18cae]{padding-bottom:52px}.search[data-v-45d18cae]{margin-left:10px}

View File

@ -1 +0,0 @@
.pagination-container[data-v-6af373ef]{background:#fff;padding:32px 16px}.pagination-container.hidden[data-v-6af373ef]{display:none}

View File

@ -0,0 +1 @@
.app-container[data-v-7aa35cbf]{position:relative;padding-bottom:60px}.el-table[data-v-7aa35cbf],.filter-container[data-v-7aa35cbf]{padding-bottom:52px}.search[data-v-7aa35cbf]{margin-left:10px}

View File

@ -1 +1 @@
.pagination-container[data-v-28fdfbeb]{padding:32px 16px;position:fixed;bottom:0;left:0;width:100%;background:#fff;padding:40px 280px;-webkit-box-shadow:0 -2px 10px rgba(0,0,0,.1);box-shadow:0 -2px 10px rgba(0,0,0,.1);z-index:100}.pagination-container.hidden[data-v-28fdfbeb]{display:none}.app-container[data-v-d6692e38]{position:relative;padding-bottom:60px}.el-table[data-v-d6692e38],.filter-container[data-v-d6692e38]{padding-bottom:52px} .pagination-container[data-v-28fdfbeb]{padding:32px 16px;position:fixed;bottom:0;left:0;width:100%;background:#fff;padding:40px 280px;-webkit-box-shadow:0 -2px 10px rgba(0,0,0,.1);box-shadow:0 -2px 10px rgba(0,0,0,.1);z-index:100}.pagination-container.hidden[data-v-28fdfbeb]{display:none}.app-container[data-v-6226c3ab]{position:relative;padding-bottom:60px}.el-table[data-v-6226c3ab],.filter-container[data-v-6226c3ab]{padding-bottom:52px}

View File

@ -0,0 +1 @@
.app-container[data-v-97931c7e]{position:relative;padding-bottom:60px}[data-v-97931c7e] .el-tabs__nav-wrap:after{display:none}.el-table[data-v-97931c7e],.filter-container[data-v-97931c7e]{padding-bottom:5px}[data-v-97931c7e] .el-row{height:50px;line-height:50px}.head_lable[data-v-97931c7e]{font-size:14px;color:#3d3d3d;font-weight:700;margin-right:10px}.scheduling_head[data-v-97931c7e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:28px}.scheduling_tab[data-v-97931c7e]{display:inline-block;padding:12px 28px;font-size:16px;border:1px solid #e2e2e2;border-radius:5px;cursor:pointer}.scheduling_tab+.scheduling_tab[data-v-97931c7e]{margin-left:10px}.active[data-v-97931c7e]{border-color:#1269ff}.el-icon-circle-plus[data-v-97931c7e]{color:#367af0;font-size:16px}.scheduling_tab_pana[data-v-97931c7e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.scheduling_tab_pana .table-list[data-v-97931c7e]{-ms-flex-preferred-size:14.3%;flex-basis:14.3%}.scheduling_tab_pana .table-list .table-list_item[data-v-97931c7e]{height:56px;background:#e4ecff;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border:1px solid #d7ddeb;margin-right:-1px;margin-bottom:-1px}.scheduling_tab_pana .table-list .table-list_item .tit[data-v-97931c7e]{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:space-evenly;-ms-flex-pack:space-evenly;justify-content:space-evenly}.scheduling_tab_pana .table-list .table-list-box[data-v-97931c7e]{padding:15px;font-size:14px;color:#fff;border:1px solid #d7ddeb;margin-right:-1px;margin-bottom:-1px}.scheduling_tab_pana .table-list .table-list-box .border[data-v-97931c7e]{padding:13px;background:#367af0;cursor:pointer}

View File

@ -1 +1 @@
.pagination-container[data-v-28fdfbeb]{padding:32px 16px;position:fixed;bottom:0;left:0;width:100%;background:#fff;padding:40px 280px;-webkit-box-shadow:0 -2px 10px rgba(0,0,0,.1);box-shadow:0 -2px 10px rgba(0,0,0,.1);z-index:100}.pagination-container.hidden[data-v-28fdfbeb]{display:none}.app-container[data-v-29e14a4a]{position:relative;padding-bottom:60px}.el-table[data-v-29e14a4a],.filter-container[data-v-29e14a4a]{padding-bottom:5px} .pagination-container[data-v-28fdfbeb]{padding:32px 16px;position:fixed;bottom:0;left:0;width:100%;background:#fff;padding:40px 280px;-webkit-box-shadow:0 -2px 10px rgba(0,0,0,.1);box-shadow:0 -2px 10px rgba(0,0,0,.1);z-index:100}.pagination-container.hidden[data-v-28fdfbeb]{display:none}.app-container[data-v-4d8f2a06]{position:relative;padding-bottom:60px}.el-table[data-v-4d8f2a06],.filter-container[data-v-4d8f2a06]{padding-bottom:5px}

View File

@ -0,0 +1 @@
@font-face{font-family:PingFang;src:url(../../static/fonts/PingFang\ SC.212ada59.ttf);font-weight:400;font-style:normal}body[data-v-2a6e779e]{font-family:PingFang!important}.infinite-list[data-v-2a6e779e]{list-style-type:none;height:calc(100vh - 154px);padding:0;margin:0}.desc_container+.desc_container[data-v-2a6e779e]{margin-top:10px;padding-top:10px}.desc_container .desc_title[data-v-2a6e779e]{font-size:18px;font-weight:500;color:#333;line-height:25px;margin-bottom:10px}.desc_container .desc_content[data-v-2a6e779e]{font-size:14px;font-weight:300}.problem .problem_form[data-v-2a6e779e]{margin-top:10px;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.problem .problem_container[data-v-2a6e779e],.problem .problem_form[data-v-2a6e779e]{display:-webkit-box;display:-ms-flexbox;display:flex}.problem .problem_container .problem_left[data-v-2a6e779e]{font-family:PingFang,sans-serif;font-weight:300;width:18%;background:#fff;padding:0 20px;border-right:2px solid #46a6ff;height:calc(100vh - 154px)}.problem .problem_container .problem_left .btn[data-v-2a6e779e]{color:#fff;padding:10px 20px;cursor:pointer;text-align:center;background:#46a6ff;border-radius:10px}.problem .problem_container .problem_left .btn+.btn[data-v-2a6e779e]{margin-top:10px}.problem .problem_container .problem_right[data-v-2a6e779e]{width:100%;background:#fff;padding:0 20px}.problem .problem_container .problem_right .problem_right_container+.problem_right_container[data-v-2a6e779e]{margin-top:20px}.problem .problem_container .problem_right .problem_right_container .title[data-v-2a6e779e]{font-size:20px;font-weight:600;margin-bottom:10px;color:#46a6ff}.problem .problem_container .problem_right .problem_right_container .title[data-v-2a6e779e]>:first-child{margin-right:40px}.problem .problem_container .problem_right .problem_right_container .desc[data-v-2a6e779e]{font-size:14px;color:#666;line-height:24px}.problem .problem_container .problem_right .problem_right_container .desc .copy-button[data-v-2a6e779e]{margin-left:300px}.image-list-horizontal[data-v-2a6e779e]{display:-webkit-box;display:-ms-flexbox;display:flex;overflow-x:auto;white-space:nowrap;padding:10px 0}.image-card[data-v-2a6e779e]{display:inline-block;width:150px;margin-right:10px}.image-preview[data-v-2a6e779e]{width:100%;height:100px;-o-object-fit:cover;object-fit:cover}.image-preview-full[data-v-2a6e779e]{width:100%;max-height:600px}.image-footer[data-v-2a6e779e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.file-list-horizontal[data-v-2a6e779e]{display:-webkit-box;display:-ms-flexbox;display:flex;overflow-x:auto;white-space:nowrap;padding:10px 0}.file-card[data-v-2a6e779e]{display:inline-block;width:400px;margin-right:10px}.file-info[data-v-2a6e779e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;overflow:hidden}.file-icon[data-v-2a6e779e]{font-size:24px;margin-right:10px}.file-name[data-v-2a6e779e]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:calc(100% - 40px)}.file-footer[data-v-2a6e779e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}

View File

@ -1 +0,0 @@
@font-face{font-family:PingFang;src:url(../../static/fonts/PingFang\ SC.212ada59.ttf);font-weight:400;font-style:normal}body[data-v-d572e954]{font-family:PingFang!important}.infinite-list[data-v-d572e954]{list-style-type:none;height:calc(100vh - 154px);padding:0;margin:0}.desc_container+.desc_container[data-v-d572e954]{margin-top:10px;padding-top:10px}.desc_container .desc_title[data-v-d572e954]{font-size:18px;font-weight:500;color:#333;line-height:25px;margin-bottom:10px}.desc_container .desc_content[data-v-d572e954]{font-size:14px;font-weight:300}.problem .problem_form[data-v-d572e954]{margin-top:10px;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.problem .problem_container[data-v-d572e954],.problem .problem_form[data-v-d572e954]{display:-webkit-box;display:-ms-flexbox;display:flex}.problem .problem_container .problem_left[data-v-d572e954]{font-family:PingFang,sans-serif;font-weight:300;width:18%;background:#fff;padding:0 20px;border-right:2px solid #46a6ff;height:calc(100vh - 154px)}.problem .problem_container .problem_left .btn[data-v-d572e954]{color:#fff;padding:10px 20px;cursor:pointer;text-align:center;background:#46a6ff;border-radius:10px}.problem .problem_container .problem_left .btn+.btn[data-v-d572e954]{margin-top:10px}.problem .problem_container .problem_right[data-v-d572e954]{width:100%;background:#fff;padding:0 20px}.problem .problem_container .problem_right .problem_right_container+.problem_right_container[data-v-d572e954]{margin-top:20px}.problem .problem_container .problem_right .problem_right_container .title[data-v-d572e954]{font-size:20px;font-weight:600;margin-bottom:10px;color:#46a6ff}.problem .problem_container .problem_right .problem_right_container .title[data-v-d572e954]>:first-child{margin-right:40px}.problem .problem_container .problem_right .problem_right_container .desc[data-v-d572e954]{font-size:14px;color:#666;line-height:24px}.problem .problem_container .problem_right .problem_right_container .desc .copy-button[data-v-d572e954]{margin-left:300px}.image-list-horizontal[data-v-d572e954]{display:-webkit-box;display:-ms-flexbox;display:flex;overflow-x:auto;white-space:nowrap;padding:10px 0}.image-card[data-v-d572e954]{display:inline-block;width:150px;margin-right:10px}.image-preview[data-v-d572e954]{width:100%;height:100px;-o-object-fit:cover;object-fit:cover}.image-preview-full[data-v-d572e954]{width:100%;max-height:600px}.image-footer[data-v-d572e954]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.file-list-horizontal[data-v-d572e954]{display:-webkit-box;display:-ms-flexbox;display:flex;overflow-x:auto;white-space:nowrap;padding:10px 0}.file-card[data-v-d572e954]{display:inline-block;width:400px;margin-right:10px}.file-info[data-v-d572e954]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;overflow:hidden}.file-icon[data-v-d572e954]{font-size:24px;margin-right:10px}.file-name[data-v-d572e954]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:calc(100% - 40px)}.file-footer[data-v-d572e954]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}

View File

@ -0,0 +1 @@
.pagination-container[data-v-6af373ef]{background:#fff;padding:32px 16px}.pagination-container.hidden[data-v-6af373ef]{display:none}[data-v-ea07b72c].el-select{width:100%}.addRoutes[data-v-ea07b72c]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:end}

View File

@ -1 +1 @@
.pagination-container[data-v-28fdfbeb]{padding:32px 16px;position:fixed;bottom:0;left:0;width:100%;background:#fff;padding:40px 280px;-webkit-box-shadow:0 -2px 10px rgba(0,0,0,.1);box-shadow:0 -2px 10px rgba(0,0,0,.1);z-index:100}.pagination-container.hidden[data-v-28fdfbeb]{display:none}.app-container[data-v-f648eafc]{position:relative;padding-bottom:60px}.el-table[data-v-f648eafc],.filter-container[data-v-f648eafc]{padding-bottom:52px} .pagination-container[data-v-28fdfbeb]{padding:32px 16px;position:fixed;bottom:0;left:0;width:100%;background:#fff;padding:40px 280px;-webkit-box-shadow:0 -2px 10px rgba(0,0,0,.1);box-shadow:0 -2px 10px rgba(0,0,0,.1);z-index:100}.pagination-container.hidden[data-v-28fdfbeb]{display:none}.app-container[data-v-79892438]{position:relative;padding-bottom:60px}.el-table[data-v-79892438],.filter-container[data-v-79892438]{padding-bottom:5px}

View File

@ -1 +1 @@
.pagination-container[data-v-28fdfbeb]{padding:32px 16px;position:fixed;bottom:0;left:0;width:100%;background:#fff;padding:40px 280px;-webkit-box-shadow:0 -2px 10px rgba(0,0,0,.1);box-shadow:0 -2px 10px rgba(0,0,0,.1);z-index:100}.pagination-container.hidden[data-v-28fdfbeb]{display:none}.app-container[data-v-71de5b60]{position:relative;padding-bottom:60px}.el-table[data-v-71de5b60],.filter-container[data-v-71de5b60]{padding-bottom:52px} .pagination-container[data-v-28fdfbeb]{padding:32px 16px;position:fixed;bottom:0;left:0;width:100%;background:#fff;padding:40px 280px;-webkit-box-shadow:0 -2px 10px rgba(0,0,0,.1);box-shadow:0 -2px 10px rgba(0,0,0,.1);z-index:100}.pagination-container.hidden[data-v-28fdfbeb]{display:none}.app-container[data-v-4c275e32]{position:relative;padding-bottom:60px}.el-table[data-v-4c275e32],.filter-container[data-v-4c275e32]{padding-bottom:52px}

View File

@ -0,0 +1 @@
.pagination-container[data-v-28fdfbeb]{padding:32px 16px;position:fixed;bottom:0;left:0;width:100%;background:#fff;padding:40px 280px;-webkit-box-shadow:0 -2px 10px rgba(0,0,0,.1);box-shadow:0 -2px 10px rgba(0,0,0,.1);z-index:100}.pagination-container.hidden[data-v-28fdfbeb]{display:none}.disable-layer[data-v-25113710]{width:100%;height:100%;background:#f5f7fa;position:absolute;top:0;left:0;z-index:99;opacity:.5}.scrollable-container[data-v-3b4f3a4e]{height:500px}[data-v-3b4f3a4e].el-scrollbar .el-scrollbar__wrap{overflow-x:hidden}.upload-list .wu-yu[data-v-3b4f3a4e],.upload-list[data-v-3b4f3a4e]{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.upload-list .wu-yu[data-v-3b4f3a4e]{margin-bottom:10px;position:relative;width:110px;-ms-flex-line-pack:justify;align-content:space-between}.upload-list .wu-yu+.wu-yu[data-v-3b4f3a4e]{margin-left:5px}.upload-list .wu-yu[data-v-3b4f3a4e] .el-input{width:100px;margin:10px 0 10px 10px}.filter-items[data-v-3b4f3a4e]{position:fixed;left:70%;z-index:66}.img-box[data-v-3b4f3a4e]{position:relative;display:inline-block;width:100px;margin-bottom:10px;margin-left:10px}.img-box+.img-box[data-v-3b4f3a4e]{margin-left:10px}.img-box .desc[data-v-3b4f3a4e]{line-height:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.avatar-uploader[data-v-3b4f3a4e] .el-upload{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.el-icon-plus[data-v-3b4f3a4e]{display:inline-block;margin-left:10px}.el-icon-folder[data-v-3b4f3a4e]{color:#409eff!important;font-size:100px}.close[data-v-3b4f3a4e]{position:absolute;top:-10px;right:-15px;font-size:18px;color:#409eff}.mistake-content[data-v-3b4f3a4e],.qa-desc[data-v-3b4f3a4e]{display:-webkit-box;display:-ms-flexbox;display:flex}.mistake-left[data-v-3b4f3a4e]{width:90%}.mistake-right[data-v-3b4f3a4e]{padding-left:20px;height:auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.app-container[data-v-3b4f3a4e]{position:relative;padding-bottom:60px}.el-table[data-v-3b4f3a4e],.filter-container[data-v-3b4f3a4e]{padding-bottom:52px}.mistake-btn[data-v-3b4f3a4e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse;margin-top:10px}.search[data-v-3b4f3a4e]{margin-left:10px}.avatar-uploader .el-upload[data-v-3b4f3a4e]{border:1px solid #131313;border-radius:6px;cursor:pointer;position:relative;overflow:hidden}.avatar-uploader .el-upload[data-v-3b4f3a4e]:hover{border-color:#409eff}.avatar-uploader-icon[data-v-3b4f3a4e]{border:1px solid #979797;border-radius:15px;font-size:28px;color:#8c939d;width:100px;height:100px;line-height:100px;text-align:center;position:relative}

View File

@ -1 +0,0 @@
.pagination-container[data-v-28fdfbeb]{padding:32px 16px;position:fixed;bottom:0;left:0;width:100%;background:#fff;padding:40px 280px;-webkit-box-shadow:0 -2px 10px rgba(0,0,0,.1);box-shadow:0 -2px 10px rgba(0,0,0,.1);z-index:100}.pagination-container.hidden[data-v-28fdfbeb]{display:none}.disable-layer[data-v-25113710]{width:100%;height:100%;background:#f5f7fa;position:absolute;top:0;left:0;z-index:99;opacity:.5}.img-box[data-v-6b889b2e]{position:relative;display:inline-block;width:100px;margin-bottom:10px}.img-box+.img-box[data-v-6b889b2e]{margin-left:10px}.img-box .desc[data-v-6b889b2e]{line-height:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.avatar-uploader[data-v-6b889b2e] .el-upload{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.el-icon-plus[data-v-6b889b2e]{display:inline-block;margin-left:10px}.el-icon-folder[data-v-6b889b2e]{color:#409eff!important;font-size:100px}.close[data-v-6b889b2e]{position:absolute;top:-10px;right:-8px;font-size:18px;color:#409eff}.mistake-content[data-v-6b889b2e],.qa-desc[data-v-6b889b2e]{display:-webkit-box;display:-ms-flexbox;display:flex}.mistake-left[data-v-6b889b2e]{width:90%}.mistake-right[data-v-6b889b2e]{padding-left:20px;height:auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.app-container[data-v-6b889b2e]{position:relative;padding-bottom:60px}.el-table[data-v-6b889b2e],.filter-container[data-v-6b889b2e]{padding-bottom:52px}.mistake-btn[data-v-6b889b2e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse;margin-top:10px}.search[data-v-6b889b2e]{margin-left:10px}.avatar-uploader .el-upload[data-v-6b889b2e]{border:1px solid #131313;border-radius:6px;cursor:pointer;position:relative;overflow:hidden}.avatar-uploader .el-upload[data-v-6b889b2e]:hover{border-color:#409eff}.avatar-uploader-icon[data-v-6b889b2e]{border:1px solid #979797;border-radius:15px;font-size:28px;color:#8c939d;width:100px;height:100px;line-height:100px;text-align:center;position:relative}

View File

@ -0,0 +1 @@
.pagination-container[data-v-28fdfbeb]{padding:32px 16px;position:fixed;bottom:0;left:0;width:100%;background:#fff;padding:40px 280px;-webkit-box-shadow:0 -2px 10px rgba(0,0,0,.1);box-shadow:0 -2px 10px rgba(0,0,0,.1);z-index:100}.pagination-container.hidden[data-v-28fdfbeb]{display:none}.app-container[data-v-78a03c33]{position:relative;padding-bottom:60px}.el-table[data-v-78a03c33],.filter-container[data-v-78a03c33]{padding-bottom:52px}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-24cd5960"],{"5c7c":function(t,e,n){"use strict";n.r(e);var i=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"app-container"},[n("el-table",{directives:[{name:"loading",rawName:"v-loading",value:t.listLoading,expression:"listLoading"}],staticStyle:{width:"100%"},attrs:{data:t.list,border:"",fit:"","highlight-current-row":""}},[n("el-table-column",{attrs:{align:"center",label:"ID",width:"60",prop:"id"}}),n("el-table-column",{attrs:{align:"center",label:"姓名",width:"80",prop:"username"}}),n("el-table-column",{attrs:{align:"center",label:"状态",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[0===e.row.isOnline?n("el-tag",{attrs:{type:"border-card"}},[t._v("下线")]):t._e(),1===e.row.isOnline?n("el-tag",{attrs:{type:"success"}},[t._v("在线")]):t._e(),2===e.row.isOnline?n("el-tag",{attrs:{type:"info"}},[t._v("没上线")]):t._e()]}}])}),n("el-table-column",{attrs:{align:"center",label:"是否分单",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[n("el-switch",{attrs:{"active-value":1,"inactive-value":0},on:{change:function(n){return t.updateStatus(e.row)}},model:{value:e.row.isEndWork,callback:function(n){t.$set(e.row,"isEndWork",n)},expression:"scope.row.isEndWork"}})]}}])}),n("el-table-column",{attrs:{align:"center",label:"在线时长",width:"120"},scopedSlots:t._u([{key:"default",fn:function(e){return[t._v(" "+t._s(Math.floor((e.row.data?e.row.data.onlineTime:e.row.onlineTime)/60)||"--")+" 分钟 ")]}}])}),n("el-table-column",{attrs:{width:"138px",align:"center",label:"上线时间"},scopedSlots:t._u([{key:"default",fn:function(e){return[n("span",[t._v(t._s(t._f("parseTime")(e.row.start_work_time,"{y}-{m}-{d} {h}:{i}")))])]}}])}),n("el-table-column",{attrs:{width:"138px",align:"center",label:"停止分单时间"},scopedSlots:t._u([{key:"default",fn:function(e){return[n("span",[t._v(t._s(t._f("parseTime")(e.row.end_work_time,"{y}-{m}-{d} {h}:{i}")))])]}}])}),n("el-table-column",{attrs:{width:"138px",align:"center",label:"下线时间"},scopedSlots:t._u([{key:"default",fn:function(e){return[n("span",[t._v(t._s(t._f("parseTime")(e.row.last_work_time,"{y}-{m}-{d} {h}:{i}")))])]}}])}),n("el-table-column",{attrs:{width:"138px",align:"center",label:"路线"},scopedSlots:t._u([{key:"default",fn:function(e){return[n("span",[t._v(t._s(t._f("parseTime")(e.row.last_work_time,"{y}-{m}-{d} {h}:{i}")))])]}}])})],1)],1)},a=[],l={name:"GetOnlineList",components:{},data:function(){return{statusArr:{0:"禁用",1:"启用"},list:[],total:0,loading:!1,listLoading:!0,listQuery:{page:1,limit:10,status:null,content:""},dialogCreate:!1,dialogEdit:!1,item:{},anchors:{}}},created:function(){this.listQuery.status=this.$route.query.status||null,this.listQuery.content=this.$route.query.content||null,this.getOnlineList()},methods:{getOnlineList:function(){var t=this;this.listLoading=!0,this.$axios.get("/admin/admin/getOnlineList",{params:this.listQuery}).then((function(e){t.list=e.data,t.listLoading=!1})).catch((function(){t.listLoading=!1}))},updateStatus:function(t){var e=this;this.$axios.post("/admin/admin/editInfo",{id:t.id,order_num:t.order_num,is_order:t.isEndWork}).then((function(){e.getOnlineList()})).catch((function(){}))}}},s=l,r=(n("89a4"),n("2877")),o=Object(r["a"])(s,i,a,!1,null,"45d18cae",null);e["default"]=o.exports},"89a4":function(t,e,n){"use strict";n("d4f7")},d4f7:function(t,e,n){}}]); (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-27f79c9e"],{"5c7c":function(t,e,n){"use strict";n.r(e);var i=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"app-container"},[n("el-table",{directives:[{name:"loading",rawName:"v-loading",value:t.listLoading,expression:"listLoading"}],staticStyle:{width:"100%"},attrs:{data:t.list,border:"",fit:"","highlight-current-row":""}},[n("el-table-column",{attrs:{align:"center",label:"ID",width:"60",prop:"id"}}),n("el-table-column",{attrs:{align:"center",label:"姓名",width:"80",prop:"username"}}),n("el-table-column",{attrs:{align:"center",label:"状态",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[0===e.row.isOnline?n("el-tag",{attrs:{type:"border-card"}},[t._v("下线")]):t._e(),1===e.row.isOnline?n("el-tag",{attrs:{type:"success"}},[t._v("在线")]):t._e(),2===e.row.isOnline?n("el-tag",{attrs:{type:"info"}},[t._v("没上线")]):t._e()]}}])}),n("el-table-column",{attrs:{align:"center",label:"是否分单",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[n("el-switch",{attrs:{"active-value":1,"inactive-value":0},on:{change:function(n){return t.updateStatus(e.row)}},model:{value:e.row.isEndWork,callback:function(n){t.$set(e.row,"isEndWork",n)},expression:"scope.row.isEndWork"}})]}}])}),n("el-table-column",{attrs:{align:"center",label:"在线时长",width:"120"},scopedSlots:t._u([{key:"default",fn:function(e){return[t._v(" "+t._s(Math.floor((e.row.data?e.row.data.onlineTime:e.row.onlineTime)/60)||"--")+" 分钟 ")]}}])}),n("el-table-column",{attrs:{width:"138px",align:"center",label:"上线时间"},scopedSlots:t._u([{key:"default",fn:function(e){return[n("span",[t._v(t._s(t._f("parseTime")(e.row.start_work_time,"{y}-{m}-{d} {h}:{i}")))])]}}])}),n("el-table-column",{attrs:{width:"138px",align:"center",label:"停止分单时间"},scopedSlots:t._u([{key:"default",fn:function(e){return[n("span",[t._v(t._s(t._f("parseTime")(e.row.end_work_time,"{y}-{m}-{d} {h}:{i}")))])]}}])}),n("el-table-column",{attrs:{width:"138px",align:"center",label:"下线时间"},scopedSlots:t._u([{key:"default",fn:function(e){return[n("span",[t._v(t._s(t._f("parseTime")(e.row.last_work_time,"{y}-{m}-{d} {h}:{i}")))])]}}])})],1)],1)},a=[],l={name:"GetOnlineList",components:{},data:function(){return{statusArr:{0:"禁用",1:"启用"},list:[],total:0,loading:!1,listLoading:!0,listQuery:{page:1,limit:10,status:null,content:""},dialogCreate:!1,dialogEdit:!1,item:{},anchors:{}}},created:function(){this.listQuery.status=this.$route.query.status||null,this.listQuery.content=this.$route.query.content||null,this.getOnlineList()},methods:{getOnlineList:function(){var t=this;this.listLoading=!0,this.$axios.get("/admin/admin/getOnlineList",{params:this.listQuery}).then((function(e){t.list=e.data,t.listLoading=!1})).catch((function(){t.listLoading=!1}))},updateStatus:function(t){var e=this;this.$axios.post("/admin/admin/editInfo",{id:t.id,order_num:t.order_num,is_order:t.isEndWork}).then((function(){e.getOnlineList()})).catch((function(){}))}}},s=l,o=(n("d8ad"),n("2877")),r=Object(o["a"])(s,i,a,!1,null,"7aa35cbf",null);e["default"]=r.exports},a659:function(t,e,n){},d8ad:function(t,e,n){"use strict";n("a659")}}]);

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0de3a1"],{"856d":function(t,e,l){"use strict";l.r(e);var i=function(){var t=this,e=t.$createElement,l=t._self._c||e;return l("div",{staticClass:"app-container"},[l("div",{staticClass:"filter-container"},[l("el-date-picker",{staticClass:"filter-item",attrs:{type:"datetimerange","default-time":["00:00:00","23:59:59"],"range-separator":"至","start-placeholder":"开始日期","end-placeholder":"结束日期"},model:{value:t.listQuery.times,callback:function(e){t.$set(t.listQuery,"times",e)},expression:"listQuery.times"}}),l("el-select",{staticClass:"filter-item",attrs:{filterable:"",placeholder:"请选择"},model:{value:t.listQuery.os,callback:function(e){t.$set(t.listQuery,"os",e)},expression:"listQuery.os"}},[l("el-option",{key:"",attrs:{label:"请选择",value:""}}),t._l(t.oss,(function(t,e){return l("el-option",{key:e,attrs:{label:t,value:e}})}))],2),l("el-button",{staticClass:"filter-item",attrs:{type:"primary",icon:"el-icon-search"},on:{click:t.getList}},[t._v(" 搜索 ")])],1),l("el-table",{directives:[{name:"loading",rawName:"v-loading",value:t.listLoading,expression:"listLoading"}],staticStyle:{width:"100%"},attrs:{data:t.list,border:"",fit:"","highlight-current-row":""}},[l("el-table-column",{attrs:{align:"center",fixed:"",label:"姓名",width:"120",prop:"admin.name"}}),l("el-table-column",{attrs:{align:"center",label:"订单数",width:"120",prop:"orders"}}),l("el-table-column",{attrs:{align:"center",label:"订单总金额"},scopedSlots:t._u([{key:"default",fn:function(e){return[t._v(" "+t._s(e.row.total_price/100)+" ")]}}])}),l("el-table-column",{attrs:{align:"center",label:"核销数",width:"120",prop:"assets"}}),l("el-table-column",{attrs:{align:"center",label:"核销金额"},scopedSlots:t._u([{key:"default",fn:function(e){return[t._v(" "+t._s(e.row.asset_price/100)+" ")]}}])}),l("el-table-column",{attrs:{align:"center",label:"未付款订单",width:"120",prop:"nopays"}}),l("el-table-column",{attrs:{align:"center",label:"未付款金额"},scopedSlots:t._u([{key:"default",fn:function(e){return[t._v(" "+t._s(e.row.nopay_price/100)+" ")]}}])}),l("el-table-column",{attrs:{align:"center",label:"核销率(按订单)",width:"160"},scopedSlots:t._u([{key:"default",fn:function(e){return[t._v(" "+t._s(e.row.write_rate)+"% ")]}}])}),l("el-table-column",{attrs:{align:"center",width:"180",label:"核销率(按销售额)"},scopedSlots:t._u([{key:"default",fn:function(e){return[t._v(" "+t._s(e.row.write_rate_price)+"% ")]}}])})],1)],1)},a=[],s=l("5530"),n=(l("a15b"),l("d81d"),l("b64b"),{name:"Datalist",data:function(){return{oss:null,list:[],listLoading:!0,listQuery:{}}},created:function(){this.getList()},methods:{getList:function(t){var e=this;if(1!=t)this.listQuery.excel=0,this.$axios.get("/admin/data/index",{params:this.listQuery}).then((function(t){e.list=t.data,e.oss=t.ext.oss,e.listLoading=!1}));else{if(this.listQuery.excel=1,!this.listQuery.times)return void this.$message({message:"请选择日期",type:"warning"});var l=this.listQuery.times[0]instanceof Date,i=Object(s["a"])(Object(s["a"])({},this.listQuery),{},{times:[l?this.listQuery.times[0].toISOString():"",l?this.listQuery.times[1].toISOString():""]});window.open("/admin/data/index?"+this.objectToQuery(i))}},objectToQuery:function(t){return Object.keys(t).map((function(e){var l=t[e];return void 0==l||null==l?"":encodeURIComponent(e)+"="+encodeURIComponent(l)})).join("&")}}}),r=n,o=l("2877"),c=Object(o["a"])(r,i,a,!1,null,null,null);e["default"]=c.exports}}]);

View File

@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0de3a1"],{"856d":function(t,e,l){"use strict";l.r(e);var i=function(){var t=this,e=t.$createElement,l=t._self._c||e;return l("div",{staticClass:"app-container"},[l("div",{staticClass:"filter-container"},[l("el-date-picker",{staticClass:"filter-item",attrs:{type:"datetimerange","default-time":["00:00:00","23:59:59"],"range-separator":"至","start-placeholder":"开始日期","end-placeholder":"结束日期"},model:{value:t.listQuery.times,callback:function(e){t.$set(t.listQuery,"times",e)},expression:"listQuery.times"}}),l("el-select",{staticClass:"filter-item",attrs:{filterable:"",placeholder:"请选择"},model:{value:t.listQuery.os,callback:function(e){t.$set(t.listQuery,"os",e)},expression:"listQuery.os"}},[l("el-option",{key:"",attrs:{label:"请选择",value:""}}),t._l(t.oss,(function(t,e){return l("el-option",{key:e,attrs:{label:t,value:e}})}))],2),l("el-button",{staticClass:"filter-item",attrs:{type:"primary",icon:"el-icon-search"},on:{click:t.getList}},[t._v(" 搜索 ")]),l("el-button",{staticClass:"filter-item",attrs:{type:"primary",icon:"el-icon-search"},on:{click:function(e){return t.getList(1)}}},[t._v(" 导出 ")])],1),l("el-table",{directives:[{name:"loading",rawName:"v-loading",value:t.listLoading,expression:"listLoading"}],staticStyle:{width:"100%"},attrs:{data:t.list,border:"",fit:"","highlight-current-row":""}},[l("el-table-column",{attrs:{align:"center",fixed:"",label:"姓名",width:"120",prop:"admin.name"}}),l("el-table-column",{attrs:{align:"center",label:"订单数",width:"120",prop:"orders"}}),l("el-table-column",{attrs:{align:"center",label:"订单总金额"},scopedSlots:t._u([{key:"default",fn:function(e){return[t._v(" "+t._s(e.row.total_price/100)+" ")]}}])}),l("el-table-column",{attrs:{align:"center",label:"核销数",width:"120",prop:"assets"}}),l("el-table-column",{attrs:{align:"center",label:"核销金额"},scopedSlots:t._u([{key:"default",fn:function(e){return[t._v(" "+t._s(e.row.asset_price/100)+" ")]}}])}),l("el-table-column",{attrs:{align:"center",label:"未付款订单",width:"120",prop:"nopays"}}),l("el-table-column",{attrs:{align:"center",label:"未付款金额"},scopedSlots:t._u([{key:"default",fn:function(e){return[t._v(" "+t._s(e.row.nopay_price/100)+" ")]}}])}),l("el-table-column",{attrs:{align:"center",label:"核销率(按订单)",width:"160"},scopedSlots:t._u([{key:"default",fn:function(e){return[t._v(" "+t._s(e.row.write_rate)+"% ")]}}])}),l("el-table-column",{attrs:{align:"center",width:"180",label:"核销率(按销售额)"},scopedSlots:t._u([{key:"default",fn:function(e){return[t._v(" "+t._s(e.row.write_rate_price)+"% ")]}}])}),l("el-table-column",{attrs:{align:"center",label:"当月核销率(按订单)",width:"160"},scopedSlots:t._u([{key:"default",fn:function(e){return[t._v(" "+t._s(e.row.month_write_rate)+"% ")]}}])}),l("el-table-column",{attrs:{align:"center",width:"180",label:"当月核销率(按销售额)"},scopedSlots:t._u([{key:"default",fn:function(e){return[t._v(" "+t._s(e.row.month_write_rate_price)+"% ")]}}])})],1)],1)},a=[],n=l("5530"),s=(l("a15b"),l("d81d"),l("b64b"),{name:"Datalist",data:function(){return{oss:null,list:[],listLoading:!0,listQuery:{}}},created:function(){this.getList()},methods:{getList:function(t){var e=this;if(1!=t)this.listQuery.excel=0,this.$axios.get("/admin/data/index",{params:this.listQuery}).then((function(t){e.list=t.data,e.oss=t.ext.oss,e.listLoading=!1}));else{if(this.listQuery.excel=1,!this.listQuery.times)return void this.$message({message:"请选择日期",type:"warning"});var l=this.listQuery.times[0]instanceof Date,i=Object(n["a"])(Object(n["a"])({},this.listQuery),{},{times:[l?this.listQuery.times[0].toISOString():"",l?this.listQuery.times[1].toISOString():""]});window.open("/admin/data/index?"+this.objectToQuery(i))}},objectToQuery:function(t){return Object.keys(t).map((function(e){var l=t[e];return void 0==l||null==l?"":encodeURIComponent(e)+"="+encodeURIComponent(l)})).join("&")}}}),r=s,o=l("2877"),c=Object(o["a"])(r,i,a,!1,null,null,null);e["default"]=c.exports}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1,28 @@
{"status":200,"message":"\u6210\u529f","data":{"sellerOrderResultDOS":[],"showVerify":false,"page":{"currentPage":2,"pageSize":10,"totalCount":0,"noMore":false},"canVerify":false,"canBatchDelivery":false,"canAsyncExportOrder":true},"success":true,"traceId":"EL2AgICAsoKsChjdAyDS_sLB8TEowpfGgQ8="} {"status":200,"message":"\u6210\u529f","data":{"sellerOrderResultDOS":[],"showVerify":false,"page":{"currentPage":2,"pageSize":10,"totalCount":0,"noMore":false},"canVerify":false,"canBatchDelivery":false,"canAsyncExportOrder":true},"success":true,"traceId":"EL2AgICAsoKsChjdAyDS_sLB8TEowpfGgQ8="}
ALTER TABLE `travel`.`orders`
MODIFY COLUMN `appointment_status` tinyint NOT NULL DEFAULT 0 COMMENT '是否已预约0-未预约1-已预约未处理2-已预约已处理)' AFTER `next_remind_time`;
ALTER TABLE `admins`
ADD COLUMN `type` tinyint NOT NULL DEFAULT 0 COMMENT '类型0-管理员1-客服2-主播3-中控)' AFTER `route_type`;
ALTER TABLE `orders`
ADD COLUMN `live_room_works` int NOT NULL DEFAULT 0 COMMENT '排班表id' AFTER `appointment_status`;
2024/10/13
ALTER TABLE `orders`
ADD COLUMN `is_refunded` tinyint NOT NULL DEFAULT 0 COMMENT '是否已退款0-否1-是)' AFTER `update_time`;
update
`orders`
set is_refunded = 1
WHERE
`os` IN ( '1', '7' ) and order_status = 5;
update
`orders`
set is_refunded = 1
WHERE
(`os` IN ( '3', '5' ) and order_status = 4)

View File

@ -526,3 +526,35 @@ function input(string $param = null, $default = null)
{ {
return is_null($param) ? request()->all() : request()->input($param, $default); return is_null($param) ? request()->all() : request()->input($param, $default);
} }
/**
* 获取一周的日期
* @return array
*/
function get_week_days() {
// 获取当前日期
$today = new DateTime();
// 获取当前周的周一
$monday = clone $today;
$monday->modify('monday this week');
// 获取当前周的周日
$sunday = clone $today;
$sunday->modify('sunday this week');
// 初始化日期数组
$dates = [];
$dayNmae = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期天'];
// 从周一到周日循环
for ($i = 0; $i < 7; $i++) {
$date = clone $monday;
$date->modify("+$i days");
$dates[] = [
'date_name' => $dayNmae[$i],
'date' => $date->format('Y-m-d')
];
}
return $dates;
}