Commit 36a7999b by haojie

1

parent a059b05e
<svg width="13" height="13" viewBox="0 0 13 13" fill="" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_533_3515)">
<path d="M0.8125 6.90625C0.56875 6.90625 0.40625 6.74375 0.40625 6.5C0.40625 6.25625 0.56875 6.09375 0.8125 6.09375H12.1875C12.4312 6.09375 12.5938 6.25625 12.5938 6.5C12.5938 6.74375 12.4312 6.90625 12.1875 6.90625H0.8125Z" fill=""/>
<path d="M12.1875 5.6875H0.8125C0.325 5.6875 0 6.0125 0 6.5C0 6.9875 0.325 7.3125 0.8125 7.3125H12.1875C12.675 7.3125 13 6.9875 13 6.5C13 6.0125 12.675 5.6875 12.1875 5.6875Z" fill=""/>
<path d="M6.5 12.5938C6.25625 12.5938 6.09375 12.4312 6.09375 12.1875V0.8125C6.09375 0.56875 6.25625 0.40625 6.5 0.40625C6.74375 0.40625 6.90625 0.56875 6.90625 0.8125V12.1875C6.90625 12.4312 6.74375 12.5938 6.5 12.5938Z" fill=""/>
<path d="M6.5 0C6.0125 0 5.6875 0.325 5.6875 0.8125V12.1875C5.6875 12.675 6.0125 13 6.5 13C6.9875 13 7.3125 12.675 7.3125 12.1875V0.8125C7.3125 0.325 6.9875 0 6.5 0Z" fill=""/>
</g>
<defs>
<clipPath id="clip0_533_3515">
<rect width="13" height="13" fill="white"/>
</clipPath>
</defs>
</svg>
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.0781 4.46484C11.4883 4.46484 4.49609 11.4531 4.49609 20.0469C4.5 28.6367 11.4883 35.625 20.0781 35.625C28.668 35.625 35.6562 28.6367 35.6562 20.0469C35.6562 11.457 28.668 4.46484 20.0781 4.46484ZM26.8359 20.9453L17.0742 26.582C16.3828 26.9805 15.5195 26.4805 15.5195 25.6836V14.4102C15.5195 13.6133 16.3828 13.1133 17.0742 13.5117L26.8359 19.1484C27.5273 19.5469 27.5273 20.5469 26.8359 20.9453Z" fill="#00DDDD"/>
</svg>
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 4C11.2 4 4 11.2 4 20C4 28.8 11.2 36 20 36C28.8 36 36 28.8 36 20C36 11.2 28.8 4 20 4ZM18.2 26.6C18.2 27.2 17.6 27.6 16.8 27.6C16 27.6 15.4 27.2 15.4 26.6V13.4C15.4 12.8 16 12.4 16.8 12.4C17.6 12.4 18.2 12.8 18.2 13.4V26.6ZM24.8 26.6C24.8 27.2 24.2 27.6 23.4 27.6C22.6 27.6 22 27.2 22 26.6V13.4C22 12.8 22.6 12.4 23.4 12.4C24.2 12.4 24.8 12.8 24.8 13.4V26.6Z" fill="#00DDDD"/>
</svg>
<svg width="13" height="13" viewBox="0 0 13 13" fill="" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_533_3511)">
<path d="M11.7551 2.68587C11.7447 2.70017 11.72 2.71445 11.707 2.72614L8.5684 5.83877V10.9507C8.5684 11.5392 8.11761 12.0172 7.52783 12.0172C7.25501 12.0172 7.02377 11.9133 6.82501 11.7262C6.81048 11.7169 6.79698 11.706 6.78475 11.6938L5.91175 10.8312C5.80393 10.7246 5.80264 10.5519 5.90916 10.4427L6.2807 10.0686C6.38592 9.96598 6.5548 9.96468 6.66133 10.0673L7.45898 10.8935C7.47066 10.9039 7.47587 10.9143 7.49015 10.9273C7.48235 8.73315 7.47976 6.12067 7.47976 5.59845V5.5062C7.47976 5.43346 7.54471 5.35941 7.59669 5.30874L10.9509 1.98307C10.96 1.97137 10.9704 1.93889 10.9808 1.93889H2.03007C2.04047 1.93889 2.03916 1.97007 2.04696 1.98177L5.35315 5.31393C5.40511 5.36589 5.4103 5.43475 5.4103 5.5075V7.69387C5.4103 7.69647 5.4181 7.70037 5.4181 7.70296C5.41161 7.99915 5.18036 8.2343 4.88937 8.2343C4.59318 8.2343 4.32037 7.99265 4.32037 7.69647V5.83877L1.24282 2.72095C1.23114 2.70926 1.25062 2.68977 1.24153 2.67677C1.05315 2.47542 0.947936 2.21041 0.949231 1.935C0.949231 1.34652 1.4273 0.847656 2.01449 0.847656H10.9977C11.2822 0.847656 11.5498 0.978875 11.7512 1.18022C11.9525 1.38158 12.0564 1.6596 12.0564 1.9441C12.0539 2.21819 11.9434 2.4871 11.7551 2.68587ZM4.92835 8.45254C5.25571 8.46422 5.51163 8.73834 5.49994 9.06571C5.48956 9.3775 5.23882 9.62692 4.92835 9.63731C4.60097 9.62563 4.34505 9.35151 4.35674 9.02414C4.36714 8.71366 4.61656 8.46293 4.92835 8.45254Z" fill=""/>
</g>
<defs>
<clipPath id="clip0_533_3511">
<rect width="13" height="13" fill=""/>
</clipPath>
</defs>
</svg>
<svg width="13" height="13" viewBox="0 0 13 13" fill="" xmlns="http://www.w3.org/2000/svg">
<path d="M6.5 10.9984C5.75827 10.9967 5.02862 10.8144 4.37671 10.4677C3.7248 10.1211 3.1711 9.62104 2.76545 9.01263H1.58364C2.03747 9.91005 2.7383 10.6653 3.60702 11.1932C4.47574 11.7211 5.47777 12.0005 6.5 12C7.88607 11.9883 9.21737 11.4682 10.2314 10.5423C11.2453 9.61631 11.8684 8.35173 11.9777 6.99789H13L11.4991 5.00053L9.99818 6.99789H10.9732C10.8624 8.08784 10.3444 9.09923 9.51875 9.83768C8.69306 10.5761 7.61793 10.9895 6.5 10.9984ZM6.5 1C5.20355 1.01004 3.9519 1.46599 2.96366 2.2882C1.97541 3.11041 1.31338 4.24663 1.09318 5.49842H0L1.50091 7.50158L3.00182 5.49842H2.10955C2.33169 4.51785 2.88702 3.64021 3.68461 3.00916C4.48221 2.37811 5.47483 2.03103 6.5 2.02474C7.14541 2.02578 7.7829 2.16401 8.36848 2.42988C8.95407 2.69576 9.4738 3.08295 9.89182 3.56474L10.0868 3.51842H11.1032C10.6157 2.75166 9.93781 2.11848 9.13281 1.67806C8.32781 1.23764 7.42199 1.00436 6.5 1Z" fill=""/>
</svg>
...@@ -5,31 +5,194 @@ ...@@ -5,31 +5,194 @@
<span class="page-header-title">{{ form.title }}</span> <span class="page-header-title">{{ form.title }}</span>
<span class="page-header-title_2">{{ form.title_2 }}</span> <span class="page-header-title_2">{{ form.title_2 }}</span>
</div> </div>
<div></div> <div class="admin-header-tool">
<template v-if="form.refresh">
<CustomButtom @click="onReload">
刷新
<template #icon> <ResetSvg></ResetSvg> </template>
</CustomButtom>
</template>
<template v-if="form.filter">
<CustomButtom>
筛选
<template #icon> <FilterSvg></FilterSvg> </template>
</CustomButtom>
</template>
<template v-if="form.add">
<CustomButtom>
新增
<template #icon> <AddSvg></AddSvg> </template>
</CustomButtom>
</template>
</div>
</div>
<template
v-if="form.filter && form.filter_option && form.filter_option.length"
>
<div class="admin-filter-box">
<div class="admin-filter-child">
<template v-for="item in form.filter_option" :key="item.name">
<template v-if="item.type == 'select'">
<CustomSelect
:options="item.options"
:item="item"
v-model="item.value"
></CustomSelect>
</template>
</template>
<div class="admin-filter-tool">
<CustomButtom @click="onReset">
刷新
<template #icon> <ResetSvg></ResetSvg> </template>
</CustomButtom>
<CustomButtom @click="onFilter">
筛选
<template #icon> <FilterSvg></FilterSvg> </template>
</CustomButtom>
</div>
</div>
</div>
</template>
<div class="admin-table">
<CustomTable
:list="list"
:api="form.table_api"
:api_type="form.table_api_type"
:columns="form.table_columns"
:local_key="form.local_key"
:edit_page="form.edit_page"
@ChangeList="ChangeList"
>
<template #title="{ row }">
<slot name="title" :row="row"></slot>
</template>
<template #mp3="{ row }">
<slot name="mp3" :row="row"></slot>
</template>
</CustomTable>
</div> </div>
<slot></slot> <slot></slot>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import CustomButtom from './button.vue';
import CustomSelect from '@/components/Admin/select.vue';
import ResetSvg from '@/assets/svg/admin/reload.svg';
import FilterSvg from '@/assets/svg/admin/filter.svg';
import AddSvg from '@/assets/svg/admin/add.svg';
import { useStore } from 'vuex';
import { getFilterParams } from '@/constants/admin_form';
import CustomTable from './table.vue';
const props = defineProps<{ const props = defineProps<{
form: any; form: any;
list: any[];
}>(); }>();
const emit = defineEmits(['reset', 'update:list']);
const store = useStore();
// 重新加载页面
const onReload = () => {
store.commit('progress/reload');
};
// 清空所有筛选的值并重新请求
const onReset = () => {
emit('reset');
};
// 筛选
const onFilter = () => {
const { filter_option } = props.form;
if (filter_option) {
const params = getFilterParams(filter_option);
// 提交
}
};
// 表格数据改变
const ChangeList = (list: any[]) => {
emit('update:list', list);
};
</script> </script>
<style lang="less"> <style lang="less">
.custom-admin-page { .custom-admin-page {
padding: 20px 30px 0 30px; padding: 20px 30px 0 30px;
display: flex;
flex-direction: column;
height: 100%;
.admin-page-header { .admin-page-header {
display: flex;
justify-content: space-between;
.page-header-title { .page-header-title {
font-weight: 400; font-weight: 400;
font-size: 24px; font-size: 24px;
} }
.page-header-title_2 { .page-header-title_2 {
margin-left: 12px;
font-weight: 400; font-weight: 400;
font-size: 15px; font-size: 15px;
color: #c5c5c5; color: #c5c5c5;
} }
.admin-header-tool {
& > :not(:last-child) {
margin-right: 12px;
}
.custom-admin-button {
.left-icon {
svg {
fill: white;
transition: all 0.3s;
}
}
&:hover {
svg {
fill: black;
transition: all 0.3s;
}
}
}
}
}
.admin-filter-box {
background: #212121;
padding: 40px 20px;
border-radius: 6px;
margin-top: 12px;
.admin-filter-child {
display: flex;
flex-wrap: wrap;
row-gap: 20px;
margin-left: -30px;
& > * {
margin: 0 30px;
}
.admin-filter-tool {
& > :not(:last-child) {
margin-right: 12px;
}
.custom-admin-button {
.left-icon {
svg {
fill: white;
transition: all 0.3s;
}
}
&:hover {
svg {
fill: black;
transition: all 0.3s;
}
}
}
}
}
}
.admin-table {
background: #2d2d2d;
flex: 1;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
padding: 20px;
} }
} }
</style> </style>
<template>
<div class="custom-admin-page-edit">
<div class="admin-page-header">
<div>
<span class="page-header-title"></span>
<span class="page-header-title_2"></span>
</div>
</div>
</div>
</template>
<script lang="ts" setup></script>
<style lang="less">
.custom-admin-page-edit {
}
</style>
<template>
<div class="audio-player">
<img
:src="imgs.start"
alt=""
class="play-icon"
@click="onPlay"
v-if="!playStatus"
/>
<img :src="imgs.stop" alt="" class="play-icon" @click="onPause" v-else />
<!-- <span class="play-time">
{{ transTime(audioCurrent) }}/{{ transTime(audioDuration) }}
</span> -->
<!-- <div class="play-progress">
<div
class="play-current-progress"
:style="{ width: `${playProgress}%` }"
></div>
</div> -->
<!-- <img
src="@/assets/images/audio/voice-open.png"
alt=""
class="play-voice"
v-if="audioVolume === 1"
@click="onSetVolume(0)"
/>
<img
src="@/assets/images/audio/voice-close.png"
alt=""
class="play-voice"
v-else
@click="onSetVolume(1)"
/> -->
</div>
<audio ref="audioRef" :src="url" @canplay="onCanplay"></audio>
</template>
<script setup lang="ts">
import { ref, onBeforeMount, onBeforeUnmount } from 'vue';
withDefaults(
defineProps<{
url: string;
}>(),
{
url: 'https://xxx.xxx.xx.xx/audio.mp3',
}
);
const imgs = {
start: new URL('../../assets/svg/admin/audioStart.svg', import.meta.url).href,
stop: new URL('../../assets/svg/admin/audioStop.svg', import.meta.url).href,
};
const speedList = [
{
label: '2x',
value: 2,
},
{
label: '1.5x',
value: 1.5,
},
{
label: '1x',
value: 1,
},
{
label: '0.75x',
value: 0.75,
},
];
onBeforeMount(() => {
clearInterval(timeInterval.value);
});
onBeforeUnmount(() => {
clearInterval(timeInterval.value);
});
const speedVisible = ref<boolean>(false); // 设置音频播放速度弹窗
const audioRef = ref(); // 音频标签对象
const activeSpeed = ref(1); // 音频播放速度
const audioDuration = ref(0); // 音频总时长
const audioCurrent = ref(0); // 音频当前播放时间
const audioVolume = ref(1); // 音频声音,范围 0-1
const playStatus = ref<boolean>(false); // 音频播放状态:true 播放,false 暂停
const playProgress = ref(0); // 音频播放进度
const timeInterval = ref(); // 获取音频播放进度定时器
// 音频加载完毕的回调
const onCanplay = () => {
audioDuration.value = audioRef?.value.duration || 0;
};
const onPlay = async () => {
// 音频播放完后,重新播放
if (playProgress.value === 100) audioRef.value.currentTime = 0;
await audioRef.value.play();
playStatus.value = true;
audioDuration.value = audioRef.value.duration;
timeInterval.value = setInterval(() => {
audioCurrent.value = audioRef.value.currentTime;
playProgress.value = (audioCurrent.value / audioDuration.value) * 100;
if (playProgress.value === 100) onPause();
}, 100);
};
const onPause = () => {
audioRef.value.pause();
playStatus.value = false;
clearInterval(timeInterval.value);
};
const onChangeSpeed = (value: number) => {
activeSpeed.value = value;
// 设置倍速
audioRef.value.playbackRate = value;
speedVisible.value = false;
};
const onHandleSpeed = () => {
speedVisible.value = !speedVisible.value;
};
// 设置声音
const onSetVolume = (value: number) => {
audioRef.value.volume = value;
audioVolume.value = value;
};
// 音频播放时间换算
const transTime = (value: number) => {
let time = '';
let h = parseInt(String(value / 3600));
value %= 3600;
let m = parseInt(String(value / 60));
let s = parseInt(String(value % 60));
if (h > 0) {
time = formatTime(h + ':' + m + ':' + s);
} else {
time = formatTime(m + ':' + s);
}
return time;
};
// 格式化时间显示,补零对齐
const formatTime = (value: string) => {
let time = '';
let s = value.split(':');
let i = 0;
for (; i < s.length - 1; i++) {
time += s[i].length == 1 ? '0' + s[i] : s[i];
time += ':';
}
time += s[i].length == 1 ? '0' + s[i] : s[i];
return time;
};
</script>
<style lang="less" scoped>
.audio-player {
height: 52px;
border-radius: 8px;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
.play-icon {
width: 34px;
height: 34px;
margin-right: 7px;
cursor: pointer;
}
.play-time {
width: 72px;
display: inline-block;
margin-right: 16px;
}
.play-progress {
width: 160px;
height: 4px;
background-color: #323547;
box-shadow: inset 0px 1px 0px 0px #20222d;
border-radius: 2px;
margin-right: 16px;
position: relative;
.play-current-progress {
height: 4px;
background: #00e5ff;
border-radius: 2px;
position: absolute;
top: 0;
left: 0;
}
}
.play-voice {
width: 20px;
height: 20px;
margin-right: 14px;
cursor: pointer;
}
.play-speed {
cursor: pointer;
color: #00e5ff;
}
}
</style>
<template>
<TButton
class="custom-admin-button"
:style="{ width: width, fontWeight: bold ? 600 : 500 }"
><slot></slot>
<template #icon>
<span class="left-icon">
<slot name="icon"></slot>
</span>
</template>
</TButton>
</template>
<script lang="ts" setup>
import { Button as TButton } from 'tdesign-vue-next';
const props = withDefaults(
defineProps<{
width?: string;
bold?: boolean;
}>(),
{
width: '',
}
);
</script>
<style lang="less" scoped>
@import '@/style/variables.less';
.custom-admin-button {
height: 30px;
background-color: #2d2d2d;
border-radius: 3px;
border: none;
box-sizing: border-box;
--ripple-color: none !important;
transition: all 0.3s;
box-shadow: none;
font-weight: 400;
font-size: 12px;
&:hover {
background: @main-color-1;
transition: all 0.3s;
color: #000000;
}
.left-icon {
padding-right: 6px;
display: flex;
justify-content: center;
align-items: center;
}
}
</style>
<template>
<div class="custom-admin-select">
<TSelect
v-model="SelectValue"
:placeholder="item.placeholder"
:popupProps="{
overlayClassName: [className, 'admin-select-popup'],
}"
>
<template #prefixIcon>
<div class="select-label">
{{ item.label }}
</div>
</template>
<t-option
v-for="item in options"
:key="item.value"
:value="item.value"
:label="item.label"
></t-option>
</TSelect>
</div>
</template>
<script lang="ts" setup>
import { Select as TSelect, Option as TOption } from 'tdesign-vue-next';
import { ref, watch } from 'vue';
const props = withDefaults(
defineProps<{
item: any;
options: any[];
modelValue: number | string;
width?: string;
className?: string;
}>(),
{
width: '50%',
}
);
const emit = defineEmits(['update:modelValue']);
const SelectValue = ref(props.modelValue);
watch(
() => SelectValue.value,
(v) => {
emit('update:modelValue', v);
}
);
watch(
() => props.modelValue,
(v) => {
SelectValue.value = v;
}
);
</script>
<style lang="less">
.admin-select-popup {
border: none;
.t-popup__content {
border: none;
background: #000000;
}
.t-select__list {
padding: 0;
.t-select-option {
color: white;
--ripple-color: #00dddd;
height: 40px;
}
.t-select-option__hover,
.t-is-selected {
color: #00dddd;
background: #161616 !important;
}
}
}
.custom-admin-select {
.select-label {
color: white;
width: 60px;
display: flex;
justify-content: center;
align-items: center;
border-right: 1px solid #464646;
height: 35px;
}
.t-select__wrap {
width: 357px;
max-width: 357px;
height: 35px;
.t-select-input {
height: 100%;
.t-input__wrap {
height: 100%;
.t-input {
height: 100%;
border-radius: 6px;
background: #181818;
border: none;
.t-input__inner {
text-align: center;
color: #ffffff;
&::placeholder {
color: #888fa1;
}
}
}
.t-is-focused {
box-shadow: 0px 0px 0px 1px #00dddd;
}
.t-fake-arrow {
color: #00dddd;
}
}
}
}
}
</style>
<template>
<div>
<TTable
class="admin-real-table"
row-key="index"
:data="list"
:columns="columns"
:loading="loading"
>
<template #title="{ row }">
<slot name="title" :row="row"></slot>
</template>
<template #mp3="{ row }">
<slot name="mp3" :row="row"></slot>
</template>
<template #edit="{ row }">
<span class="edit-text" @click="onEdit(row)">编辑</span>
</template>
</TTable>
<div class="custom-pagination">
<t-pagination
v-model="pageNum"
v-model:page-size="pageSize"
:total="total"
:pageSizeOptions="[]"
@current-change="pageChange"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { Table as TTable } from 'tdesign-vue-next';
import { onBeforeMount, reactive, ref, watch } from 'vue';
import { getGenerateRecords } from '@/utils/api/scenes';
import { Pagination as TPagination } from 'tdesign-vue-next';
import { TASKTYPE } from '@/utils/api/Task';
import { getLocalData, setLocalData } from '@/utils/tool';
import { show_message } from '@/utils/tdesign_tool';
import { useRouter } from 'vue-router';
import { edit_page_key } from '@/constants/token';
const props = defineProps<{
list: any[];
api: string;
api_type: string;
columns: any[];
local_key: string;
edit_page: string;
}>();
const emit = defineEmits(['ChangeList']);
const router = useRouter();
// 获取本地的page
const getPage = () => {
const page = getLocalData(props.local_key, 'session');
return page ? page : 1;
};
const loading = ref(false);
const pageNum = ref<number>(getPage());
const pageSize = ref<number>(10);
const total = ref<number>(0);
const pageChange = (value: number) => {
pageNum.value = value;
// 存本地
setLocalData(props.local_key, value, 'session');
getLog();
};
// 打开编辑页面
const onEdit = (row: any) => {
const { edit_page } = props;
if (edit_page) {
// 行数据存本地
setLocalData(edit_page_key, JSON.stringify(row), 'session');
router.push({
path: edit_page,
});
} else {
show_message('缺少编辑配置');
}
};
// 获取表格数据
const getLog = async () => {
try {
loading.value = true;
// let res: any = await getGenerateRecords({
// page: pageNum.value,
// limit: pageSize.value,
// type: TASKTYPE.CHAT,
// });
// if (res.code == 0) {
// RecordList.list = res.data.data;
// total.value = res.data.total;
// }
// console.log(res);
// 假数据
const list = [
{
title: '你好',
detail: '详细',
consumes_characters: '消耗',
download: '编辑',
mp3: new URL('../../assets/mp3/test.aac', import.meta.url).href,
},
];
emit('ChangeList', list);
loading.value = false;
} catch (e) {
loading.value = false;
console.log(e);
}
};
onBeforeMount(() => {
getLog();
});
</script>
<style lang="less">
@import '@/style/variables.less';
.admin-real-table {
background-color: transparent;
min-height: 500px;
.t-table__content {
background-color: transparent;
}
thead {
tr {
background-color: transparent;
th {
border: none;
background: #000000;
color: white;
font-size: @font-size-16;
font-weight: 400;
}
& > :first-child {
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
& > :last-child {
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
}
}
}
tbody {
.t-table__empty-row {
background-color: transparent;
&:hover {
background-color: transparent;
}
.t-table__empty {
color: white;
}
}
tr {
background: transparent;
td {
color: #ffffff;
border-bottom: 1px solid #464646;
font-weight: 400;
font-size: @font-size-14;
.edit-text {
cursor: pointer;
}
}
}
}
.t-loading--center {
background: rgba(255, 255, 255, 0.1);
}
}
.custom-pagination {
padding: 12px 0;
.t-pagination__total {
color: white;
}
.t-is-current {
background: #00f9f9;
border-radius: 2px;
border: none;
color: #000000;
}
.t-pagination__btn {
& > :nth-child(1) {
color: #c9cdd4;
}
}
}
</style>
// 清空admin-form的value值
export const ResetForm = (list: any[]) => {
list.forEach((item: any) => {
if (typeof item.value == 'string') {
item.value = '';
}
});
return true;
};
// 过滤filter列表
export const getFilterParams = (list: any[]) => {
const params: any = {};
list.forEach((item: any) => {
if (item.value) {
params[item.name] = item.value;
}
});
return params;
};
...@@ -7,6 +7,8 @@ export const emailReg = /^\w{3,}(\.\w+)*@[A-z0-9]+(\.[A-z]{2,8}){1,2}$/; ...@@ -7,6 +7,8 @@ export const emailReg = /^\w{3,}(\.\w+)*@[A-z0-9]+(\.[A-z]{2,8}){1,2}$/;
// 记住密码存入本地的key // 记住密码存入本地的key
export const remember_password_phone = 'remember_phone'; export const remember_password_phone = 'remember_phone';
export const remember_password_email = 'remember_email'; export const remember_password_email = 'remember_email';
// 话术编辑页面本地key
export const edit_page_key = 'discourse_management_edit';
// 图片详情展示的key // 图片详情展示的key
export const image_local_key = 'local_photo_gallery'; export const image_local_key = 'local_photo_gallery';
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<div class="custom-layout"> <div class="custom-layout">
<Header v-if="route.meta.header"></Header> <Header v-if="route.meta.header"></Header>
<div class="custom-content narrow-scrollbar" :id="PageScrollId"> <div class="custom-content narrow-scrollbar" :id="PageScrollId">
<div class="center-box"> <div class="center-box" :style="getStyle()">
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<component :is="Component" /> <component :is="Component" />
</router-view> </router-view>
...@@ -16,6 +16,17 @@ import Header from './header.vue'; ...@@ -16,6 +16,17 @@ import Header from './header.vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { PageScrollId } from '@/constants/token'; import { PageScrollId } from '@/constants/token';
const route = useRoute(); const route = useRoute();
const getStyle = () => {
let params: any = {};
const style = route.meta.style;
if (style) {
if (style.maxWidth) {
params.maxWidth = style.maxWidth;
}
params.padding = style.padding ?? '';
}
return params;
};
</script> </script>
<style lang="less"> <style lang="less">
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
v-for="item in btns" v-for="item in btns"
:key="item.path" :key="item.path"
@click="changeBtn(item)" @click="changeBtn(item)"
:class="{ active: item.path === currentBtn }" :class="{ active: isActive(item) }"
> >
{{ item.label }} {{ item.label }}
</div> </div>
...@@ -87,6 +87,15 @@ const changeBtn = (item: any) => { ...@@ -87,6 +87,15 @@ const changeBtn = (item: any) => {
path: item.path, path: item.path,
}); });
}; };
const isActive = (item: any) => {
if (item.path == '/AILiveStreaming') {
// 包含就行
return currentBtn.value.indexOf(item.path) !== -1;
} else {
return currentBtn.value == item.path;
}
};
</script> </script>
<style lang="less"> <style lang="less">
......
@import '@/style/color.less'; @import '@/style/color.less';
.center-box {
max-width: none !important;
padding: 0 !important;
}
.custom-nav { .custom-nav {
position: relative; position: relative;
height: 100%; height: 100%;
...@@ -80,6 +76,25 @@ ...@@ -80,6 +76,25 @@
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
z-index: 1000; z-index: 1000;
position: relative;
.admin-page-progress {
width: 100%;
position: absolute;
top: 0;
left: 0;
.t-progress {
border: none;
box-shadow: none;
.t-progress--thin {
height: 6px;
border: none;
box-shadow: none;
}
.t-progress__info {
display: none;
}
}
}
} }
} }
@media (max-device-width: @max-device-width-1) { @media (max-device-width: @max-device-width-1) {
......
...@@ -5,11 +5,18 @@ import RaffleSvg from '@/assets/svg/tab/raffle.svg'; ...@@ -5,11 +5,18 @@ import RaffleSvg from '@/assets/svg/tab/raffle.svg';
import LpSvg from '@/assets/svg/tab/lp.svg'; import LpSvg from '@/assets/svg/tab/lp.svg';
import { RouterView, useRoute, useRouter } from 'vue-router'; import { RouterView, useRoute, useRouter } from 'vue-router';
import { getCurrentDevice, getLocalData, setLocalData } from '@/utils/tool'; import { getCurrentDevice, getLocalData, setLocalData } from '@/utils/tool';
import { Progress as TProgress } from 'tdesign-vue-next';
import { useStore } from 'vuex';
export default defineComponent({ export default defineComponent({
setup(props) { setup(props) {
let navStatusKey = 'navstatus'; let navStatusKey = 'navstatus';
const store = useStore();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
// 刷新页面
const is_load = computed(() => store.getters['progress/load']);
// 页面进度条状态
const ProgressStatus = computed(() => store.getters['progress/progress']);
const tabOptions = computed(() => [ const tabOptions = computed(() => [
{ {
icon: <HomeSvg></HomeSvg>, icon: <HomeSvg></HomeSvg>,
...@@ -20,7 +27,7 @@ export default defineComponent({ ...@@ -20,7 +27,7 @@ export default defineComponent({
{ {
icon: <RaffleSvg></RaffleSvg>, icon: <RaffleSvg></RaffleSvg>,
label: '互动设置', label: '互动设置',
value: '/raffle', value: '/AILiveStreaming/InteractiveSetting',
disable: false, disable: false,
}, },
{ {
...@@ -40,6 +47,32 @@ export default defineComponent({ ...@@ -40,6 +47,32 @@ export default defineComponent({
// 跳转路由 // 跳转路由
return cur_route; return cur_route;
}; };
onMounted(() => {
// 缓存里的路由
const local_router = getCurrentRoute();
// 当前路由
const cur_router = route.path;
let replace_router = '';
const edit_pages = ['/AILiveStreaming/DiscourseManagement/edit'];
if (edit_pages.findIndex((item: any) => item == cur_router) !== -1) {
// 存在edit页面
return;
}
if (local_router) {
// edit时不能切换
if (local_router !== cur_router) {
replace_router = local_router;
}
} else {
// 本地没有缓存
replace_router = tabOptions.value[0].value;
}
router.replace({
path: replace_router,
});
});
// 当前路由 // 当前路由
const currentRoute = ref<string>(getCurrentRoute()); const currentRoute = ref<string>(getCurrentRoute());
// 本地路由key // 本地路由key
...@@ -175,7 +208,18 @@ export default defineComponent({ ...@@ -175,7 +208,18 @@ export default defineComponent({
</div> </div>
</div> </div>
<div class="custom-content custom-scrollbar"> <div class="custom-content custom-scrollbar">
<RouterView></RouterView> {ProgressStatus.value.show ? (
<div class="admin-page-progress">
<TProgress
percentage={ProgressStatus.value.percentage}
color="#00dddd"
trackColor="transparent"
></TProgress>
</div>
) : (
''
)}
{is_load.value ? <RouterView></RouterView> : ''}
</div> </div>
</div> </div>
</header> </header>
......
<template> <template>
<AdminPublicPageVue :form="ManagementForm"> <AdminPublicPageVue
<div class="custom-discourse-management"></div> :form="ManagementForm"
v-model:list="ManagementForm.table_list"
@reset="ResetFilter"
>
<template #title>
<div>111</div>
</template>
<template #mp3="{ row }">
<CustomAudio :url="row.mp3"></CustomAudio>
</template>
</AdminPublicPageVue> </AdminPublicPageVue>
<!--
<div class="custom-discourse-management"></div>
-->
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import CustomAudio from '@/components/Admin/audio.vue';
import AdminPublicPageVue from '@/components/Admin/AdminPublicPage.vue'; import AdminPublicPageVue from '@/components/Admin/AdminPublicPage.vue';
import { reactive } from 'vue'; import { reactive } from 'vue';
import { isDevContext } from '@/utils/tool';
import { ResetForm } from '@/constants/admin_form';
import { AdminFilterApi } from '@/utils/api/ai';
// 表单配置项 // 表单配置项
const ManagementForm = reactive({ const ManagementForm = reactive({
title: '话术列表', title: '话术列表',
...@@ -15,8 +31,47 @@ const ManagementForm = reactive({ ...@@ -15,8 +31,47 @@ const ManagementForm = reactive({
refresh: true, refresh: true,
// 能否筛选 // 能否筛选
filter: true, filter: true,
// 筛选接口
filter_api: isDevContext() ? '' : '',
// 能否新增 // 能否新增
add: true, add: true,
// 表格key--用来缓存表格分页
local_key: 'admin_discourse_management',
// 表格数据
table_list: [],
// 表格接口
table_api: isDevContext() ? '' : '',
// api类型
table_api_type: 'get',
// 编辑页面
edit_page: '/AILiveStreaming/DiscourseManagement/edit',
// 表格columns-列
table_columns: [
{
title: '产品',
colKey: 'title',
},
{
title: '详细',
colKey: 'detail',
align: 'center',
},
{
title: '消耗字符数',
colKey: 'consumes_characters',
align: 'center',
},
{
title: '音频',
colKey: 'mp3',
align: 'center',
},
{
title: '操作',
colKey: 'edit',
align: 'right',
},
],
filter_option: [ filter_option: [
{ {
type: 'select', type: 'select',
...@@ -24,6 +79,16 @@ const ManagementForm = reactive({ ...@@ -24,6 +79,16 @@ const ManagementForm = reactive({
label: '标题', label: '标题',
placeholder: '请选择标题', placeholder: '请选择标题',
value: '', value: '',
options: [
{
label: '你好',
value: '你好',
},
{
label: '你好2',
value: '你好2',
},
],
}, },
{ {
type: 'select', type: 'select',
...@@ -31,6 +96,16 @@ const ManagementForm = reactive({ ...@@ -31,6 +96,16 @@ const ManagementForm = reactive({
label: '产品', label: '产品',
placeholder: '产品', placeholder: '产品',
value: '', value: '',
options: [
{
label: '你好',
value: '你好',
},
{
label: '你好2',
value: '你好2',
},
],
}, },
{ {
type: 'select', type: 'select',
...@@ -38,9 +113,27 @@ const ManagementForm = reactive({ ...@@ -38,9 +113,27 @@ const ManagementForm = reactive({
label: '语言', label: '语言',
placeholder: '语言', placeholder: '语言',
value: '', value: '',
options: [
{
label: '你好',
value: '你好',
},
{
label: '你好2',
value: '你好2',
},
],
}, },
], ],
}); });
// 重置表单并重新请求
const ResetFilter = () => {
ResetForm(ManagementForm.filter_option);
// 请求接口
// AdminFilterApi();
};
</script> </script>
<style lang="less"> <style lang="less">
......
<template>
<div class="">
111
<PageEdit></PageEdit>
</div>
</template>
<script lang="ts" setup>
import PageEdit from '@/components/Admin/PageEdit.vue';
import { onBeforeMount, reactive } from 'vue';
import { edit_page_key } from '@/constants/token';
import { getLocalData } from '@/utils/tool';
import { show_message } from '@/utils/tdesign_tool';
import { useRouter } from 'vue-router';
const router = useRouter();
// 编辑页面组件
const ManagementForm = reactive({
title: '话术管理',
title_2: '编辑',
});
const getEditData = () => {
let local_data = getLocalData(edit_page_key, 'session');
if (!local_data) {
// 没有本地数据,返回页面
show_message('禁止访问');
// 返回话术管理
router.replace({
path: '/AILiveStreaming/DiscourseManagement',
});
} else {
// 有数据
console.log(local_data);
}
};
onBeforeMount(() => {
// 本地是否有缓存
getEditData();
});
</script>
<style lang="less"></style>
<template>
<div class="">111</div>
</template>
<script lang="ts" setup></script>
<style lang="less"></style>
import store from '@/store';
/**
* 页面加载进度条工具
*/
const progress: any = {};
// 刷新显示进度条的页面
const page = [
'/AILiveStreaming/DiscourseManagement',
'/AILiveStreaming/InteractiveSetting',
];
// 初始化进度条
const initProgress = () => {
store.commit('progress/progress', {
status: true,
percentage: 0,
});
};
// 修改进度条状态
const changeProgress = (obj: any) => {
store.commit('progress/progress', obj);
};
let interval: any = null;
// 关闭定时器
const closeInterval = () => {
if (interval) {
window.clearInterval(interval);
interval = null;
}
};
// 进度条开始
progress.start = (path: string) => {
// 是否显示页面加载进度条
const index = page.findIndex((item: any) => item == path);
if (index !== -1) {
initProgress();
// 开始自增
interval = window.setInterval(() => {
const CurrentProgress = store.getters['progress/progress'];
if (CurrentProgress.percentage <= 75) {
changeProgress({
status: true,
percentage: CurrentProgress.percentage + 1,
});
} else {
closeInterval();
}
}, 10);
}
};
// 进度条结束
progress.end = () => {
changeProgress({
status: false,
percentage: 100,
});
closeInterval();
};
export default progress;
import router from '@/router'; import router from '@/router';
import progress from './PageProgress';
// import { getUserCookie } from '@/utils/api/userApi'; // import { getUserCookie } from '@/utils/api/userApi';
router.beforeEach((to: any, from: any, next: any) => { router.beforeEach((to: any, from: any, next: any) => {
// if (to.name == 'login') { // if (to.name == 'login') {
...@@ -11,5 +12,11 @@ router.beforeEach((to: any, from: any, next: any) => { ...@@ -11,5 +12,11 @@ router.beforeEach((to: any, from: any, next: any) => {
// next('/login'); // next('/login');
// return; // return;
// } // }
// progress.start(to.path);
next(); next();
}); });
router.afterEach((to: any, form: any, next: any) => {
// progress.end();
});
...@@ -126,14 +126,35 @@ export default [ ...@@ -126,14 +126,35 @@ export default [
component: () => import('@/layout/tab'), component: () => import('@/layout/tab'),
meta: { meta: {
header: true, header: true,
style: {
maxWidth: 'none',
padding: 0,
},
}, },
children: [ children: [
// 话术管理
{ {
path: '/AILiveStreaming/DiscourseManagement', path: '/AILiveStreaming/DiscourseManagement',
name: 'DiscourseManagement', name: 'DiscourseManagement',
component: () => component: () =>
import('@/pages/AILiveStreaming/DiscourseManagement/index.vue'), import('@/pages/AILiveStreaming/DiscourseManagement/index.vue'),
}, },
// 话术编辑 DiscourseManagementEdit
{
path: '/AILiveStreaming/DiscourseManagement/edit',
name: 'DiscourseManagementEdit',
component: () =>
import(
'@/pages/AILiveStreaming/DiscourseManagementEdit/index.vue'
),
},
// 互动设置
{
path: '/AILiveStreaming/InteractiveSetting',
name: 'InteractiveSetting',
component: () =>
import('@/pages/AILiveStreaming/InteractiveSetting/index.vue'),
},
], ],
}, },
], ],
......
import { createStore } from 'vuex'; import { createStore } from 'vuex';
import user from './modules/user'; import user from './modules/user';
import progress from './modules/progress';
export const store = createStore({ export const store = createStore({
modules: { modules: {
user, user,
progress,
}, },
}); });
......
// 页面进度条是否显示
interface MyState {
CurrentProgress: {
show: boolean;
percentage: number;
};
is_load: boolean;
}
// 定义的state初始值
const state: MyState = {
CurrentProgress: {
show: false,
percentage: 0,
},
is_load: true,
};
type StateType = typeof state;
const mutations = {
progress(state: StateType, obj: any) {
state.CurrentProgress.show = obj.status;
state.CurrentProgress.percentage = obj.percentage;
},
reload(state: StateType) {
state.is_load = false;
setTimeout(() => {
state.is_load = true;
}, 0);
},
};
const getters = {
progress: (state: StateType) => {
return state.CurrentProgress;
},
load: (state: StateType) => {
return state.is_load;
},
};
const actions = {};
export default {
namespaced: true,
state,
mutations,
actions,
getters,
};
...@@ -28,3 +28,8 @@ ...@@ -28,3 +28,8 @@
@font-size-26: 26px; @font-size-26: 26px;
@font-size-40: 40px; @font-size-40: 40px;
@font-size-50: 50px; @font-size-50: 50px;
/**
* 主题色
*/
@main-color-1: #00dddd;
import request from '@/utils/request';
export const AdminFilterApi = () => {
return request.get('ddd');
};
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body></body>
<script></script>
</html>
...@@ -164,13 +164,13 @@ export const getLocalData = ( ...@@ -164,13 +164,13 @@ export const getLocalData = (
) => { ) => {
if (type == 'local') { if (type == 'local') {
const data = localStorage.getItem(key); const data = localStorage.getItem(key);
if (data) { if (data && data !== 'undefined') {
return JSON.parse(data); return JSON.parse(data);
} }
return ''; return '';
} else if (type == 'session') { } else if (type == 'session') {
const data = sessionStorage.getItem(key); const data = sessionStorage.getItem(key);
if (data) { if (data && data !== 'undefined') {
return JSON.parse(data); return JSON.parse(data);
} }
return ''; return '';
...@@ -231,3 +231,9 @@ export const getCurrentDevice = () => { ...@@ -231,3 +231,9 @@ export const getCurrentDevice = () => {
return false; return false;
} }
}; };
// 是否本地环境
export const isDevContext = () => {
const is_dev = import.meta.env.DEV;
return is_dev ? true : false;
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment