Merge remote-tracking branch 'origin/main' into feature/0905_登录注册流程重构

# Conflicts:
#	src/App.vue
#	src/views/components/login/index.vue
#	src/views/components/management/person/index.vue
#	src/views/login/style.scss
This commit is contained in:
rd
2025-09-10 16:16:34 +08:00
233 changed files with 5564 additions and 6102 deletions

View File

@ -1,23 +1,25 @@
<template>
<a-config-provider :locale="zhCN" size="small" :theme="redTheme">
<router-view v-if="$route.path === '/login' || ['ExploreList', 'ExploreDetail', 'Trial'].includes($route.name)" />
<ConfigProvider :locale="zhCN" :theme="redTheme">
<router-view v-if="$route.path === '/login' || ['ExploreList', 'ExploreDetail'].includes($route.name)" />
<LayoutBasic v-else />
</a-config-provider>
</ConfigProvider>
</template>
<script setup>
import { useUserStore } from '@/stores';
import { useChatStore } from '@/stores/modules/chat';
// import { useChatStore } from '@/stores/modules/chat';
import { initApp } from '@/utils/user';
import { useSidebarStore } from '@/stores/modules/side-bar';
import zhCN from '@arco-design/web-vue/es/locale/lang/zh-cn';
import { ConfigProvider } from 'ant-design-vue';
import zhCN from 'ant-design-vue/es/locale/zh_CN';
import 'dayjs/locale/zh-cn';
const userStore = useUserStore();
const route = useRoute();
// const route = useRoute();
const sidebarStore = useSidebarStore();
const chatStore = useChatStore();
// const chatStore = useChatStore();
const redTheme = {
token: {

View File

@ -8,7 +8,7 @@
import router from '@/router';
import { clearToken } from '@/utils/auth';
import { Message } from '@arco-design/web-vue';
import { message } from 'ant-design-vue';
/**
* 处理业务逻辑定义的错误code
@ -28,5 +28,5 @@ export const handleCodeError = (error: any) => {
default:
errMessage = error.msg || `未知错误-${error.code}`;
}
Message.error(errMessage);
message.error(errMessage);
};

View File

@ -7,6 +7,7 @@
*/
import axios from 'axios';
import { message } from 'ant-design-vue';
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import { handleUserLogout, goUserLogin } from '@/utils/user';
import { useEnterpriseStore } from '@/stores/modules/enterprise';
@ -84,7 +85,7 @@ export class Request {
break;
}
AMessage.error(errMessage);
message.error(errMessage);
return Promise.reject(err.response);
},
);

View File

@ -1,2 +0,0 @@
export { default as TabBar } from './tab-bar/index.vue';
export { default as ModalSimple } from './modal/index.vue';

View File

@ -1,32 +0,0 @@
<script setup lang="ts">
import type { Component, DefineComponent } from 'vue';
import IconHover from '@arco-design/web-vue/es/_components/icon-hover';
defineProps<{
title?: string;
content?: string | (() => DefineComponent | Component);
}>();
defineEmits(['close']);
</script>
<template>
<slot name="header">
<div class="flex justify-end mb7">
<slot name="close">
<icon-hover @click="$emit('close')">
<icon-close />
</icon-hover>
</slot>
</div>
</slot>
<slot>
<div class="flex flex-col text-center">
<div v-if="title" class="mb4 text-lg font-600">{{ title }}</div>
<template v-else />
<component :is="content" v-if="typeof content === 'function'" />
<div v-else>{{ content }}</div>
</div>
</slot>
</template>

View File

@ -1,83 +0,0 @@
<script lang="ts" setup>
import type { RouteLocationNormalized } from 'vue-router';
import { listenerRouteChange, removeRouteListener } from '@/utils/route-listener';
import { useAppStore, useTabBarStore } from '@/stores';
import TabItem from './tab-item.vue';
const appStore = useAppStore();
const tabBarStore = useTabBarStore();
const affixRef = ref();
const tagList = computed(() => {
return tabBarStore.getTabList;
});
const offsetTop = computed(() => {
return appStore.navbar ? 60 : 0;
});
watch(
() => appStore.navbar,
() => {
affixRef.value.updatePosition();
},
);
listenerRouteChange((route: RouteLocationNormalized) => {
if (!route.meta.noAffix && !tagList.value.some((tag) => tag.fullPath === route.fullPath)) {
tabBarStore.updateTabList(route);
}
}, true);
onUnmounted(() => {
removeRouteListener();
});
</script>
<template>
<div class="tab-bar-container">
<a-affix ref="affixRef" :offset-top="offsetTop">
<div class="tab-bar-box">
<div class="tab-bar-scroll">
<div class="tags-wrap">
<tab-item v-for="(tag, index) in tagList" :key="tag.fullPath" :index="index" :item-data="tag" />
</div>
</div>
<div class="tag-bar-operation"></div>
</div>
</a-affix>
</div>
</template>
<style scoped lang="scss">
.tab-bar-container {
position: relative;
background-color: var(--color-bg-2);
.tab-bar-box {
display: flex;
padding: 0 0 0 20px;
background-color: var(--color-bg-2);
border-bottom: 1px solid var(--color-border);
.tab-bar-scroll {
height: 32px;
flex: 1;
overflow: hidden;
.tags-wrap {
padding: 4px 0;
height: 48px;
white-space: nowrap;
overflow-x: auto;
:deep(.arco-tag) {
display: inline-flex;
align-items: center;
margin-right: 6px;
cursor: pointer;
&:first-child {
.arco-tag-close-btn {
display: none;
}
}
}
}
}
}
.tag-bar-operation {
width: 100px;
height: 32px;
}
}
</style>

View File

@ -1,177 +0,0 @@
<script lang="ts" setup>
import type { PropType } from 'vue';
import type { TagProps } from '@/stores/modules/tab-bar/types';
import { useTabBarStore } from '@/stores';
import { DEFAULT_ROUTE_NAME, REDIRECT_ROUTE_NAME } from '@/router/constants';
const props = defineProps({
itemData: {
type: Object as PropType<TagProps>,
default() {
return [];
},
},
index: {
type: Number,
default: 0,
},
});
// eslint-disable-next-line no-shadow
enum Eaction {
reload = 'reload',
current = 'current',
left = 'left',
right = 'right',
others = 'others',
all = 'all',
}
const router = useRouter();
const route = useRoute();
const tabBarStore = useTabBarStore();
const goto = (tag: TagProps) => {
router.push({ ...tag });
};
const tagList = computed(() => {
return tabBarStore.getTabList;
});
const disabledReload = computed(() => {
return props.itemData.fullPath !== route.fullPath;
});
const disabledCurrent = computed(() => {
return props.index === 0;
});
const disabledLeft = computed(() => {
return [0, 1].includes(props.index);
});
const disabledRight = computed(() => {
return props.index === tagList.value.length - 1;
});
const tagClose = (tag: TagProps, idx: number) => {
tabBarStore.deleteTag(idx, tag);
if (props.itemData.fullPath === route.fullPath) {
const latest = tagList.value[idx - 1]; // 获取队列的前一个tab
router.push({ name: latest.name });
}
};
const findCurrentRouteIndex = () => {
return tagList.value.findIndex((el) => el.fullPath === route.fullPath);
};
const actionSelect = async (value: any) => {
const { itemData, index } = props;
const copyTagList = [...tagList.value];
if (value === Eaction.current) {
tagClose(itemData, index);
} else if (value === Eaction.left) {
const currentRouteIdx = findCurrentRouteIndex();
copyTagList.splice(1, props.index - 1);
tabBarStore.freshTabList(copyTagList);
if (currentRouteIdx < index) {
router.push({ name: itemData.name });
}
} else if (value === Eaction.right) {
const currentRouteIdx = findCurrentRouteIndex();
copyTagList.splice(props.index + 1);
tabBarStore.freshTabList(copyTagList);
if (currentRouteIdx > index) {
router.push({ name: itemData.name });
}
} else if (value === Eaction.others) {
const filterList = tagList.value.filter((el, idx) => {
return idx === 0 || idx === props.index;
});
tabBarStore.freshTabList(filterList);
router.push({ name: itemData.name });
} else if (value === Eaction.reload) {
tabBarStore.deleteCache(itemData);
await router.push({
name: REDIRECT_ROUTE_NAME,
params: {
path: route.fullPath,
},
});
tabBarStore.addCache(itemData.name);
} else {
tabBarStore.resetTabList();
router.push({ name: DEFAULT_ROUTE_NAME });
}
};
</script>
<template>
<a-dropdown trigger="contextMenu" :popup-max-height="false" @select="actionSelect">
<span
:class="[
'arco-tag arco-tag-size-medium arco-tag-checked',
{ 'link-activated': itemData.fullPath === $route.fullPath },
]"
@click="goto(itemData)"
>
<span class="tag-link">{{ itemData.title }}</span>
<span
class="arco-icon-hover arco-tag-icon-hover arco-icon-hover-size-medium arco-tag-close-btn"
@click.stop="tagClose(itemData, index)"
>
<icon-close />
</span>
</span>
<template #content>
<a-doption :disabled="disabledReload" :value="Eaction.reload">
<icon-refresh />
<span>重新加载</span>
</a-doption>
<a-doption class="sperate-line" :disabled="disabledCurrent" :value="Eaction.current">
<icon-close />
<span>关闭当前标签页</span>
</a-doption>
<a-doption :disabled="disabledLeft" :value="Eaction.left">
<icon-to-left />
<span>关闭左侧标签页</span>
</a-doption>
<a-doption class="sperate-line" :disabled="disabledRight" :value="Eaction.right">
<icon-to-right />
<span>关闭右侧标签页</span>
</a-doption>
<a-doption :value="Eaction.others">
<icon-swap />
<span>关闭其它标签页</span>
</a-doption>
<a-doption :value="Eaction.all">
<icon-folder-delete />
<span>关闭全部标签页</span>
</a-doption>
</template>
</a-dropdown>
</template>
<style scoped lang="scss">
.tag-link {
color: var(--color-text-2);
text-decoration: none;
}
.link-activated {
color: rgb(var(--link-6));
.tag-link {
color: rgb(var(--link-6));
}
& + .arco-tag-close-btn {
color: rgb(var(--link-6));
}
}
:deep(.arco-dropdown-option-content) {
span {
margin-left: 10px;
}
}
.arco-dropdown-open {
.tag-link {
color: rgb(var(--danger-6));
}
.arco-tag-close-btn {
color: rgb(var(--danger-6));
}
}
.sperate-line {
border-bottom: 1px solid var(--color-neutral-3);
}
</style>

View File

@ -3,23 +3,26 @@
* @Date: 2025-06-25 14:02:40
-->
<template>
<a-select
v-model="selectedValues"
:multiple="multiple"
size="medium"
<Select
v-model:value="selectedValues"
:mode="multiple ? 'multiple' : undefined"
size="middle"
:placeholder="placeholder"
:allow-clear="allClear"
:allow-search="allowSearch"
:max-tag-count="maxTagCount"
:allowClear="allClear"
:showSearch="allowSearch"
showArrow
:maxTagCount="maxTagCount"
@change="handleChange"
>
<a-option v-for="(item, index) in options" :key="index" :value="item.id" :label="item.name">
<Option v-for="(item, index) in options" :key="index" :value="item.id" :label="item.name">
{{ item.name }}
</a-option>
</a-select>
</Option>
</Select>
</template>
<script setup>
import { Select } from 'ant-design-vue';
const { Option } = Select;
import { ref, watch } from 'vue';
const props = defineProps({

View File

@ -3,14 +3,15 @@
* @Date: 2025-06-30 10:54:49
-->
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
title="自定义列"
width="960px"
unmountOnClose
titleAlign="start"
class="custom-table-column-modal"
@close="close"
centered
wrapClassName="custom-table-column-modal"
@cancel="close"
>
<div class="modal-body">
<!-- 左侧分组 -->
@ -20,16 +21,16 @@
<span class="text">{{ group.label }}</span>
</div>
<div class="fields">
<a-checkbox
<Checkbox
v-for="option in group.columns"
:key="option.value"
:model-value="isCheck(option)"
:checked="isCheck(option)"
:value="option.value"
:disabled="option.is_require === ENUM_STATUS.NO"
@change="(checked) => onCheckChange(checked, option)"
@change="(e) => onCheckChange(e.target.checked, option)"
>
{{ option.label }}
</a-checkbox>
</Checkbox>
</div>
</div>
</div>
@ -64,15 +65,16 @@
</div>
<template #footer>
<div style="text-align: right">
<a-button class="mr-8px" size="medium" @click="close">取消</a-button>
<a-button type="primary" size="medium" @click="onSubmit">确定</a-button>
<div class="flex">
<Button @click="close">取消</Button>
<Button type="primary" @click="onSubmit">确定</Button>
</div>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { Checkbox, Modal, Button } from 'ant-design-vue';
import { ref, defineExpose } from 'vue';
import { VueDraggable } from 'vue-draggable-plus';

View File

@ -1,5 +1,5 @@
.custom-table-column-modal {
.arco-modal-body {
.ant-modal-body {
.modal-body {
height: 504px;
border-radius: 8px;

View File

@ -1,13 +1,31 @@
<template>
<a-modal modal-class="delete-modal" body-class="body" cancel-text="返回" ok-text="确定删除" v-bind="$attrs">
<Modal wrapClassName="delete-modal" body-class="body" v-bind="$attrs" centered title="删除账号">
<h2 class="delete-modal-title flex item-center">
<img src="@/assets/warning.svg" alt="" />
{{ $attrs.title }}
{{ $attrs.content }}
</h2>
<slot></slot>
</a-modal>
<p class="delete-modal-content">删除后该账号将无法登录您的企业</p>
<template #footer>
<div style="text-align: right">
<Button @click="close">返回</Button>
<Button type="primary" danger @click="onSubmit">确定删除</Button>
</div>
</template>
</Modal>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
import { Modal, Button } from 'ant-design-vue';
const emit = defineEmits(['close', 'ok']);
const close = () => {
emit('close');
};
const onSubmit = () => {
emit('ok');
};
</script>
<style lang="scss">
:deep(.arco-btn-status-danger) {
background-color: red !important;
@ -18,7 +36,7 @@
display: none;
}
.delete-modal-title {
margin-top: 24px;
// margin-top: 24px;
font-family: $font-family-medium;
font-weight: 400;
font-size: 14px;
@ -29,6 +47,14 @@
margin-right: 12px;
}
}
.delete-modal-content {
margin-left: 34px;
margin-top: 16px;
font-family: $font-family-medium;
font-weight: 400;
font-size: 12px;
color: var(--Text-2, rgba(60, 64, 67, 1));
}
.arco-modal-footer {
border-top: none;
:first-child {
@ -53,5 +79,6 @@
}
.body {
padding: 0 24px;
}
</style>

View File

@ -3,32 +3,45 @@
* @Date: 2025-08-11 22:15:35
-->
<template>
<a-popover
:trigger="'hover'"
class="hover-big-image-preview-popover"
:position="props.position"
:mouse-enter-delay="props.enterDelay"
:mouse-leave-delay="props.leaveDelay"
:disabled="!props.src"
<Popover
trigger="hover"
:placement="props.position"
:mouseEnterDelay="props.enterDelay / 1000"
:mouseLeaveDelay="props.leaveDelay / 1000"
:open="props.src ? undefined : false"
overlayClassName="hover-big-image-preview-popover"
>
<template #content>
<div class="preview-container">
<img :src="props.src" alt="preview" class="preview-image" />
</div>
</template>
<slot />
</a-popover>
</Popover>
</template>
<script lang="ts" setup>
import { Popover } from 'ant-design-vue';
// import { computed, onMounted, ref, watch } from 'vue';
// import type { ImageOrientation } from '@/utils/tools';
// import { getImageOrientationByUrl } from '@/utils/tools';
interface Props {
src: string;
position?: 'top' | 'tl' | 'tr' | 'bottom' | 'bl' | 'br' | 'left' | 'lt' | 'lb' | 'right' | 'rt' | 'rb';
position?:
| 'top'
| 'topLeft'
| 'topRight'
| 'bottom'
| 'bottomLeft'
| 'bottomRight'
| 'left'
| 'leftTop'
| 'leftBottom'
| 'right'
| 'rightTop'
| 'rightBottom';
enterDelay?: number;
leaveDelay?: number;
}
@ -37,6 +50,7 @@ const props = withDefaults(defineProps<Props>(), {
position: 'right',
enterDelay: 100,
leaveDelay: 200,
src: '',
});
// const orientation = ref<ImageOrientation>('landscape');
@ -67,7 +81,11 @@ const props = withDefaults(defineProps<Props>(), {
<style lang="scss">
.hover-big-image-preview-popover {
.arco-popover-popup-content {
.ant-popover-content {
padding: 0 !important;
}
.ant-popover-inner {
padding: 16px !important;
border-radius: 8px;
background: #fff;

View File

@ -0,0 +1,90 @@
<template>
<div class="img-lazy" v-lazy:background-image="imgSrc" :key="src" :class="imgClass" :style="style" />
</template>
<script setup>
import { ref, onMounted, watch, computed } from 'vue';
const emit = defineEmits(['click']);
const props = defineProps({
width: {
type: [String, Number],
},
height: {
type: [String, Number],
},
loadingSize: {
type: [String],
default: '5',
},
errorSize: {
type: [String],
default: '5',
},
fit: {
type: [String],
default: 'cover',
},
src: {
type: String,
},
customImg: {
type: String,
},
});
const style = computed(() => {
return {
'background-size': props.fit,
width: props.width ? parseInt(props.width) + 'px' : undefined,
height: props.height ? parseInt(props.height) + 'px' : undefined,
};
});
const imgClass = computed(() => {
return {
['loading-size-' + props.loadingSize]: true,
};
});
const imgSrc = computed(() => {
return props.innerSrc || props.src;
});
const innerSrc = ref('');
watch(
() => props.customImg,
() => {
innerSrc.value = '';
if (props.customImg) {
const img = new Image();
img.src = props.src;
img.onerror = () => {
innerSrc.value = props.customImg;
};
img.onload = () => {
innerSrc.value = props.src;
};
}
},
{ immediate: true },
);
onMounted(() => {});
</script>
<style scoped lang="scss">
.img-lazy.block {
display: block;
}
.img-lazy {
display: inline-block;
overflow: hidden;
background-size: 100% 100%;
background-position: center center;
background-repeat: no-repeat;
@for $i from 0 to 10 {
&.loading-size-#{$i}[lazy='loading'] {
background-size: #{$i * 10 + '%'} !important;
}
}
}
</style>

View File

@ -9,6 +9,7 @@
<script setup lang="ts">
import Modal from '@components/modal.vue';
import { ref, onMounted } from 'vue';
import { message } from 'ant-design-vue';
import { getQueryParam } from '@/utils/helper';
import { getEnterpriseByInviteCode, joinEnterpriseByInviteCode } from '@/api/all';
@ -26,7 +27,7 @@ async function getEnterprise() {
async function handleJoin() {
await joinEnterpriseByInviteCode(inviteCode.value);
AMessage.success('加入成功');
message.success('加入成功');
}
// onMounted(() => {
// getEnterprise();

View File

@ -1,9 +1,11 @@
<template>
<a-modal title-align="start" modal-class="modal" body-class="body" v-bind="$attrs">
<Modal title-align="start" wrapClassName="modal" cancelText="取消" okText="确定" body-class="body" v-bind="$attrs" centered>
<slot></slot>
</a-modal>
</Modal>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
import { Modal } from 'ant-design-vue';
</script>
<style lang="scss">
.modal {
.arco-modal-header {

View File

@ -1,18 +1,16 @@
<template>
<a-upload
:custom-request="customRequest"
<Upload
:customRequest="customRequest"
action="/"
:limit="limit"
:maxCount="limit"
:fileList="fileList"
@change="onChange"
@success="handleSuccess"
@error="handleError"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Message } from '@arco-design/web-vue';
import { Upload, message } from 'ant-design-vue';
import { fetchImageUploadFile, fetchUploadFile } from '@/api/all';
import axios from 'axios';
@ -70,8 +68,10 @@ watch(
);
let previousFileListLength = 0;
//删除图片
const onChange = (fileList) => {
const onChange = (info) => {
const { fileList } = info;
// 如果删除了文件
if (fileList.length < previousFileListLength) {
if (props.limit === 1) {
if (fileList.length === 0) {
@ -86,28 +86,35 @@ const onChange = (fileList) => {
}
}
}
// 处理上传成功的文件
if (info.file.status === 'done' && info.file.response) {
handleSuccess(info.file);
} else if (info.file.status === 'error') {
handleError(info.file.error);
}
previousFileListLength = fileList.length;
};
const beforeUpload = (file, files) => {
if (props.limit > 0 && files.length >= props.limit) {
Message.warning(`最多只能上传 ${props.limit} 张图片`);
message.warning(`最多只能上传 ${props.limit} 张图片`);
return false; // 阻止上传
}
return true;
};
const handleError = (error) => {
Message.error('上传失败');
message.error('上传失败');
console.error(error);
};
const customRequest = async (option) => {
const { onProgress, onError, onSuccess, fileItem, name } = option;
const { onProgress, onError, onSuccess, file, name } = option;
try {
// 1. 获取预签名上传URL
const response = await fetchUploadFile({ suffix: getFileExtension(fileItem.file.name) });
const response = await fetchUploadFile({ suffix: getFileExtension(file.name) });
const preSignedUrl = response?.data?.upload_url;
if (!preSignedUrl) {
@ -115,9 +122,9 @@ const customRequest = async (option) => {
}
console.log('preSignedUrl', preSignedUrl);
// 2. 使用预签名URL上传文件
const blob = new Blob([fileItem.file], { type: fileItem.file.type });
const blob = new Blob([file], { type: file.type });
await axios.put(preSignedUrl, blob, {
headers: { 'Content-Type': fileItem.file.type },
headers: { 'Content-Type': file.type },
});
onSuccess(JSON.stringify(response));

View File

@ -1,20 +1,19 @@
<template>
<a-upload
:custom-request="customRequest"
list-type="picture-card"
<Upload
:customRequest="customRequest"
listType="picture-card"
action="/"
:limit="limit"
:maxCount="limit"
:fileList="fileList"
image-preview
:showUploadList="{ showPreviewIcon: true, showRemoveIcon: true }"
@change="onChange"
@success="handleSuccess"
@error="handleError"
@preview="handlePreview"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Message } from '@arco-design/web-vue';
import { Upload, message } from 'ant-design-vue';
import { fetchImageUploadFile } from '@/api/all';
import axios from 'axios';
@ -72,8 +71,14 @@ watch(
);
let previousFileListLength = 0;
//删除图片
const onChange = (fileList) => {
const handlePreview = (file) => {
console.log('Preview file:', file);
};
const onChange = (info) => {
const { fileList } = info;
// 如果删除了文件
if (fileList.length < previousFileListLength) {
if (props.limit === 1) {
if (fileList.length === 0) {
@ -88,28 +93,35 @@ const onChange = (fileList) => {
}
}
}
// 处理上传成功的文件
if (info.file.status === 'done' && info.file.response) {
handleSuccess(info.file);
} else if (info.file.status === 'error') {
handleError(info.file.error);
}
previousFileListLength = fileList.length;
};
const beforeUpload = (file, files) => {
if (props.limit > 0 && files.length >= props.limit) {
Message.warning(`最多只能上传 ${props.limit} 张图片`);
message.warning(`最多只能上传 ${props.limit} 张图片`);
return false; // 阻止上传
}
return true;
};
const handleError = (error) => {
Message.error('上传失败');
message.error('上传失败');
console.error(error);
};
const customRequest = async (option) => {
const { onProgress, onError, onSuccess, fileItem, name } = option;
const { onProgress, onError, onSuccess, file, name } = option;
try {
// 1. 获取预签名上传URL
const response = await fetchImageUploadFile({ suffix: getFileExtension(fileItem.file.name) });
const response = await fetchImageUploadFile({ suffix: getFileExtension(file.name) });
const preSignedUrl = response?.data?.upload_url;
if (!preSignedUrl) {
@ -117,9 +129,9 @@ const customRequest = async (option) => {
}
console.log('preSignedUrl', preSignedUrl);
// 2. 使用预签名URL上传文件
const blob = new Blob([fileItem.file], { type: fileItem.file.type });
const blob = new Blob([file], { type: file.type });
await axios.put(preSignedUrl, blob, {
headers: { 'Content-Type': fileItem.file.type },
headers: { 'Content-Type': file.type },
});
onSuccess(JSON.stringify(response));

View File

@ -1,5 +1,5 @@
<script lang="tsx">
import { Button } from '@arco-design/web-vue';
import { Button } from 'ant-design-vue';
import { Bubble } from '@/components/xt-chat/xt-bubble';
import Http from '@/api';
@ -79,8 +79,8 @@ export default {
<header class="header flex justify-end items-center mb-16px px-32px">
{hasMediaCenter.value && (
<Button
type="outline"
size="medium"
type="primary"
ghost
class="mr-16px"
v-slots={{ icon: () => <icon-plus size="14" /> }}
onClick={onAddMediaCenter}
@ -90,8 +90,8 @@ export default {
)}
<Button
type="outline"
size="medium"
type="primary"
ghost
class="mr-16px"
v-slots={{ icon: () => <icon-plus size="14" /> }}
onClick={onAddTaskManage}

View File

@ -1,2 +1 @@
export * from './responsive';
export * from './modal';
export * from './responsive';

View File

@ -1,55 +0,0 @@
/*
* @Author: 田鑫
* @Date: 2023-02-21 15:11:01
* @LastEditors: 田鑫
* @LastEditTime: 2023-02-21 15:11:02
* @Description:
*/
import type { AppContext, Component, DefineComponent } from 'vue';
import type { ModalConfig } from '@arco-design/web-vue';
import { ModalSimple } from '@/components/_base';
type CompType = DefineComponent | Component;
interface SlotsType {
default?: CompType;
header?: CompType;
close?: CompType;
}
export const useModal = () => {
const instance = getCurrentInstance();
const Modal = AModal;
Modal._context = instance?.appContext as AppContext;
Modal.simple = (config: ModalConfig, slots: SlotsType) => {
const { title, content, ..._config } = config || {};
const modal = Modal.open({
..._config,
simple: false,
footer: false,
closable: false,
content: () =>
h(
ModalSimple,
{
title,
content,
onClose: modal.close,
},
{
default: slots?.default,
header: slots?.header,
close: slots?.close,
},
),
});
return modal;
};
return { Modal };
};

View File

@ -1,172 +0,0 @@
/*
* @Author: 田鑫
* @Date: 2023-02-16 15:02:51
* @LastEditors: 田鑫
* @LastEditTime: 2023-03-09 11:20:59
* @Description: table-hooks
*/
import type { PaginationProps, TableBorder, TableColumnData, TableData, TableRowSelection } from '@arco-design/web-vue';
type Size = 'mini' | 'small' | 'medium' | 'large';
interface IDefaultProps {
/** 是否显示边框 */
bordered?: TableBorder;
/** 是否显示选中效果 */
hoverable?: boolean;
/** 表格的大小 */
size?: Size;
/** 是否允许调整列宽 */
'column-resizable'?: boolean;
/** 是否为加载中状态 */
loading?: boolean;
/** 分页参数 */
pagination?: PaginationProps;
/** table数据类型 */
data?: any[];
/** 表头参数 */
columns?: TableColumnData[];
/** 表格行 key 的取值字段 */
'row-key'?: string;
/** 表格的行选择器配置 */
'row-selection'?: TableRowSelection;
'selected-keys'?: (string | number)[];
[x: string]: any;
}
interface IPagination {
/** 当前页数 */
current?: number;
/** 总页数默认是0条 */
total?: number;
}
interface ITableResponse<T> {
current?: number;
records?: T[];
size?: number;
total?: number;
[x: string]: any;
}
type GetListFunc<T> = (v: object) => Promise<ITableResponse<T>>;
export default function useTableProps<T>(loadListFunc: GetListFunc<T>) {
const defaultProps: IDefaultProps = {
bordered: { cell: true },
size: 'large',
'column-resizable': true,
loading: true,
data: [] as any[],
pagination: {
current: 1,
pageSize: 20,
total: 0,
showPageSize: true,
showTotal: true,
},
hoverable: false,
columns: [],
};
//* 属性组
const propsRes = reactive<IDefaultProps>(defaultProps);
//* 设置请求参数,如果出了分页参数还有搜索参数,在模板页面调用此方法,可以加入参数
const loadListParams = reactive<object>({
page: 1,
size: 20,
});
/**
* 单独设置默认属性
* @param params
*/
const setProps = (params: IDefaultProps) => {
if (Object.keys(params).length > 0) {
Object.assign(defaultProps, params);
}
};
/**
* 设置表头数据
* @param columns
*/
const setColumns = (columns: TableColumnData[]) => {
propsRes.columns = columns;
};
/**
* 设置loading
* @param status
*/
const setLoading = (status: boolean) => {
propsRes.loading = status;
};
/**
* 设置分页
* @param param0
*/
const setPagination = ({ current, total }: IPagination) => {
propsRes.pagination!.current = current;
total && (propsRes.pagination!.total = total);
Object.assign(loadListParams, { page: current });
};
/**
* 设置列表请求参数
* @param params
*/
const setLoadListParams = <R>(params?: R) => {
Object.assign(loadListParams, params);
};
/**
* 加载列表
* @returns
*/
const loadTableData = async (resetPageIndex = false) => {
if (resetPageIndex) {
setPagination({ current: 1 });
}
setLoading(true);
try {
const resData = await loadListFunc({
...loadListParams,
});
console.log(resData);
const response = resData as ITableResponse<T>;
propsRes.data = response.records;
setPagination({
current: response.current,
total: response.total,
});
setLoading(false);
return resData;
} catch (error) {
setLoading(false);
return [];
}
};
// 事件触发组
const propsEvent = reactive({
// 排序触发
sorterChange: (dataIndex: string, direction: string) => {
console.log(dataIndex, direction);
},
// 分页触发
pageChange: (current: number) => {
setPagination({ current });
loadTableData();
},
// 修改每页显示条数
pageSizeChange: (size: number) => {
propsRes.pagination!.pageSize = size;
Object.assign(loadListParams, { size });
loadTableData();
},
selectionChange: (rowKeys: (string | number)[]) => {
propsRes['selected-keys'] = rowKeys;
},
});
return {
propsRes,
propsEvent,
loadListParams,
setProps,
setColumns,
setLoading,
setPagination,
loadTableData,
setLoadListParams,
};
}

View File

@ -22,30 +22,28 @@ export function useTableSelectionWithPagination(options: UseTableSelectionWithPa
const rowKey = options.rowKey || 'id';
const selectedRowKeys = ref<Array<string | number>>([]);
const selectedRows = ref<any[]>([]);
const selectedRows = ref<Array<any>>([]);
const pageInfo = ref(merge({}, DEFAULT_PAGE_INFO, options.pageInfo));
const dataSource = ref<any[]>([]);
// 单行选择
const handleSelect = (selectedKeys: (string | number)[], rowKeyValue: string | number, record: any) => {
const select = selectedKeys.includes(rowKeyValue);
selectedRowKeys.value = selectedKeys;
const handleSelect = (record: any, select: boolean) => {
const _targetKey = record[rowKey];
if (select) {
if (!selectedRows.value.some((v) => v[rowKey] === record[rowKey])) {
selectedRows.value.push(record);
}
selectedRows.value.push(record);
selectedRowKeys.value.push(_targetKey);
} else {
selectedRows.value = selectedRows.value.filter((v) => v[rowKey] !== record[rowKey]);
selectedRows.value = selectedRows.value.filter((v) => v[rowKey] !== _targetKey);
selectedRowKeys.value = selectedRowKeys.value.filter((key) => key !== _targetKey);
}
options.onSelectChange?.();
};
// 全选/取消全选
const handleSelectAll = (checked: boolean) => {
console.log('handleSelectAll', checked)
const currentPageRows = dataSource.value;
const currentPageKeys = currentPageRows.map((v) => v[rowKey]);
@ -62,18 +60,21 @@ export function useTableSelectionWithPagination(options: UseTableSelectionWithPa
options.onSelectChange?.();
};
const onPageChange = (page: number) => {
const onPageChange = (page: number, pageSize: number) => {
// console.log('onPageChange', page, pageSize);
pageInfo.value.page = page;
pageInfo.value.page_size = pageSize;
options.onPageChange?.(page);
};
const onPageSizeChange = (size: number) => {
pageInfo.value.page_size = size;
pageInfo.value.page = 1;
options.onPageSizeChange?.(size);
const onPageSizeChange = (current: number, size: number) => {
// console.log('onPageSizeChange', current, size);
// pageInfo.value.page_size = size;
// pageInfo.value.page = 1;
// options.onPageSizeChange?.(size);
};
const resetPageInfo = () => {
pageInfo.value = cloneDeep(DEFAULT_PAGE_INFO)
}
pageInfo.value = cloneDeep(DEFAULT_PAGE_INFO);
};
const rowSelection = computed(() => ({
type: 'checkbox',

View File

@ -55,7 +55,7 @@ const checkHasInviteCode = () => {
<template>
<Layout :class="['layout-wrap', { mobile: appStore.hideMenu }]" class="h-full flex flex-col w-full">
<JoinModal v-model:visible="joinEnterpriseVisible" ref="joinModalRef" />
<JoinModal v-model:open="joinEnterpriseVisible" centered ref="joinModalRef" />
<Layout.Header class="layout-header-wrap">
<Navbar />
</Layout.Header>

View File

@ -1,4 +1,7 @@
<script setup lang="ts">
import { Button, Result } from 'ant-design-vue';
import { useRouter } from 'vue-router';
const router = useRouter();
const back = () => {
router.replace('/');
@ -7,9 +10,9 @@ const back = () => {
<template>
<div class="content">
<a-result class="result" status="404" subtitle="页面跑路了" />
<div class="operation-row">
<a-button key="back" type="primary" @click="back">返回</a-button>
<Result class="result" status="404" sub-title="页面跑路了" />
<div class="operation-row flex justify-center">
<Button key="back" type="primary" @click="back">返回</Button>
</div>
</div>
</template>
@ -19,8 +22,10 @@ const back = () => {
position: absolute;
top: 50%;
left: 50%;
margin-left: -95px;
margin-top: -121px;
text-align: center;
transform: translate(-50%, -50%);
padding: 32px 32px 24px;
// margin-left: -95px;
// margin-top: -121px;
// text-align: center;
}
</style>

View File

@ -3,13 +3,14 @@
* @Date: 2025-06-26 17:23:52
-->
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
width="400px"
modal-class="exit-account-modal"
wrapClassName="exit-account-modal"
show-close="false"
:footer="false"
@close="onClose"
:footer="null"
@cancel="onClose"
centered
>
<div class="flex items-center mb-16px">
<img :src="icon1" width="20" height="20" class="mr-12px" />
@ -17,15 +18,14 @@
</div>
<p class="m-0 p-0 mb-24px s2 ml-32px">退出登录后你将无法收到该账号的通知</p>
<div class="flex items-center justify-end">
<a-button class="!rounded-4px" size="medium" @click="onClose">返回</a-button>
<a-button type="primary" class="ml-16px danger-btn" status="danger" size="medium" @click="onLogout"
>退出登录</a-button
>
<Button @click="onClose">返回</Button>
<Button danger type="primary" @click="onLogout" class="ml-8px">退出登录</Button>
</div>
</a-modal>
</Modal>
</template>
<script setup>
import { Modal, Button, message } from 'ant-design-vue';
import { ref } from 'vue';
import { fetchLogOut } from '@/api/all/login';
import { handleUserLogout } from '@/utils/user';
@ -46,7 +46,7 @@ async function onLogout() {
const { code } = await fetchLogOut();
if (code === 200) {
handleUserLogout();
AMessage.success('退出登录成功');
message.success('退出登录成功');
onClose();
}
}
@ -56,14 +56,14 @@ defineExpose({ open });
<style lang="scss">
.exit-account-modal {
border-radius: 8px;
border: 1px solid var(--BG-300, #e6e6e8) !important;
background-color: var(--BG-white, #fff) !important;
box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1);
.arco-modal-header {
// border-radius: 8px;
// border: 1px solid var(--BG-300, #e6e6e8) !important;
// background-color: var(--BG-white, #fff) !important;
// box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1);
.ant-modal-header {
display: none;
}
.arco-modal-body {
.ant-modal-body {
padding: 24px;
.s1 {
color: var(--Text-1, #211f24);
@ -81,15 +81,6 @@ defineExpose({ open });
font-weight: 400;
line-height: 20px; /* 166.667% */
}
.cancel-btn {
border-radius: 4px;
}
.danger-btn {
background: var(--Functional-Danger-6, #f64b31) !important;
&:hover {
background: var(--Functional-Danger-6, #f64b31) !important;
}
}
}
}
</style>

View File

@ -10,33 +10,31 @@
<div class="agent-entry mx-16px" :class="isAgentRoute ? 'agent' : ''" @click="handleAgentClick"></div>
<!-- 头像设置 -->
<a-dropdown trigger="click" class="layout-avatar-dropdown">
<a-avatar class="cursor-pointer" :size="32">
<img alt="avatar" src="@/assets/avatar.svg" />
</a-avatar>
<template #content>
<div>
<a-doption>
<a-space class="flex justify-between w-100%" @click="setServerMenu">
<Dropdown trigger="click" overlayClassName="layout-avatar-dropdown">
<img alt="avatar" src="@/assets/avatar.svg" class="cursor-pointer w-32px h-32px rounded-50%" />
<template #overlay>
<Menu>
<MenuItem>
<div class="h-full flex justify-between items-center w-100%" @click="setServerMenu">
<div class="flex items-center">
<img :src="icon1" class="w-16px h-16px mr-8px" />
<span>管理中心</span>
</div>
<icon-right size="12" />
</a-space>
</a-doption>
<a-dsubmenu value="option-1" position="lt" trigger="hover" class="enterprises-dsubmenu">
<a-doption class="enterprises-doption">
<a-space class="flex justify-between w-100%">
<div class="flex items-center">
<img :src="icon3" class="w-16px h-16px mr-8px" />
<span>切换企业账号</span>
</div>
</MenuItem>
<MenuItem>
<SubMenu value="option-1" position="lt" trigger="hover" popupClassName="enterprises-dsubmenu">
<template #title>
<div class="flex justify-between w-100% h-full items-center">
<div class="flex items-center">
<img :src="icon3" class="w-16px h-16px mr-8px" />
<span>切换企业账号</span>
</div>
<icon-right size="12" />
</div>
<icon-right size="12" />
</a-space>
</a-doption>
<template #content>
<a-doption
</template>
<MenuItem
v-for="(item, index) in enterprises"
:key="index"
class="rounded-8px hover:bg-#F2F3F5"
@ -49,21 +47,21 @@
<span>{{ item.name }}</span>
<icon-check v-if="enterpriseInfo?.id === item.id" size="16" />
</div>
</a-doption>
</template>
</a-dsubmenu>
<a-doption>
<a-space class="flex justify-between w-100%" @click="clickExit">
</MenuItem>
</SubMenu>
</MenuItem>
<MenuItem>
<div class="flex justify-between w-100% h-full items-center" @click="clickExit">
<div class="flex items-center">
<img :src="icon2" class="w-16px h-16px mr-8px" />
<span>退出登录</span>
</div>
<icon-right size="12" />
</a-space>
</a-doption>
</div>
</div>
</MenuItem>
</Menu>
</template>
</a-dropdown>
</Dropdown>
<ExitAccountModal ref="exitAccountModalRef" />
<DownloadCenterModal ref="downloadCenterModalRef" />
@ -71,6 +69,7 @@
</template>
<script setup>
import { Dropdown, Menu, MenuItem, SubMenu } from 'ant-design-vue';
import router from '@/router';
import { useEnterpriseStore } from '@/stores/modules/enterprise';
import { useSidebarStore } from '@/stores/modules/side-bar';
@ -137,35 +136,51 @@ const handleAgentClick = () => {
<style lang="scss">
.layout-avatar-dropdown,
.enterprises-dsubmenu {
.arco-dropdown {
.ant-dropdown-menu {
border-radius: 8px;
border: 1px solid var(--BG-300, #e6e6e8);
background: var(--BG-white, #fff);
padding: 12px 0px;
.arco-dropdown-option {
.ant-dropdown-menu-item {
padding: 0 12px;
margin-bottom: 4px;
&-content {
.ant-dropdown-menu-title-content {
display: flex;
height: 40px;
width: 100%;
padding: 10px 24px;
align-items: center;
.menu-item-text {
color: var(--Text-2, #3c4043);
font-family: $font-family-regular;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 22px;
.ant-dropdown-menu-submenu {
width: 100%;
.ant-dropdown-menu-submenu-title {
padding: 0;
&:hover {
background: none;
}
.ant-dropdown-menu-title-content {
padding: 0 !important;
}
}
.ant-dropdown-menu-submenu-arrow {
display: none;
}
}
}
.arco-dropdown-option-content {
.menu-item-text {
color: var(--Text-2, #3c4043);
font-family: $font-family-regular;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 22px;
}
.ant-dropdown-menu-title-content {
border-radius: 8px !important;
}
&:not(.arco-dropdown-option-disabled):hover {
&:not(.ant-dropdown-menu-item):hover {
background-color: transparent;
.arco-dropdown-option-content {
.ant-dropdown-menu-title-content {
background: var(--BG-200, #f2f3f5);
}
}
@ -175,29 +190,29 @@ const handleAgentClick = () => {
.layout-avatar-dropdown,
.enterprises-dsubmenu {
width: 200px;
.arco-dropdown {
.ant-dropdown-menu {
padding: 12px 4px;
.arco-dropdown-option {
.ant-dropdown-menu-item {
padding: 0 !important;
&-content {
.ant-dropdown-menu-title-content {
padding: 0 12px !important;
}
}
}
.arco-dropdown-option-suffix {
.ant-dropdown-option-suffix {
display: none;
}
.enterprises-doption {
.arco-dropdown-option-content {
padding: 0 !important;
border-radius: 8px;
}
&:not(.arco-dropdown-option-disabled):hover {
background-color: transparent;
.arco-dropdown-option-content {
background: var(--BG-200, #f2f3f5);
}
}
}
// .enterprises-doption {
// .ant-dropdown-menu-title-content {
// padding: 0 !important;
// border-radius: 8px;
// }
// &:not(.ant-dropdown-option-disabled):hover {
// background-color: transparent;
// .ant-dropdown-menu-title-content {
// background: var(--BG-200, #f2f3f5);
// }
// }
// }
}
</style>

View File

@ -1,24 +1,24 @@
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
:title="isBatch ? '批量删除下载记录' : '删除下载记录'"
width="400px"
@close="onClose"
@cancel="onClose"
centered
>
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认删除 {{ accountName }} 这条记录吗</span>
</div>
<template #footer>
<a-button size="large" @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px !bg-#f64b31 !border-none" status="danger" size="large" @click="onDelete"
>确定</a-button
>
<Button @click="onClose">取消</Button>
<Button type="primary" danger @click="onDelete">确定</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { Modal, Button, message } from 'ant-design-vue';
import { ref } from 'vue';
import { deleteTask, deleteBatchTasks } from '@/api/all/common';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
@ -52,7 +52,7 @@ async function onDelete() {
const { code } = await _fn(_params);
if (code === 200) {
AMessage.success('删除成功');
message.success('删除成功');
isBatch.value ? emits('batchUpdate') : emits('update');
onClose();
}

View File

@ -1,7 +1,8 @@
<script lang="jsx">
import { ref, computed } from 'vue';
import { Input, Table, TableColumn, Checkbox, Pagination, Button, Tooltip, Notification } from '@arco-design/web-vue';
import { Button, Checkbox, Input, Tooltip, Table, Pagination, message, notification } from 'ant-design-vue';
import { IconSearch, IconClose, IconQuestionCircle } from '@arco-design/web-vue/es/icon';
import NoData from '@/components/no-data';
import { getTask, postRedoTask, postBatchDownload, batchQueryTaskStatus } from '@/api/all/common';
import { INITIAL_FORM, TABLE_COLUMNS } from './constants';
@ -21,8 +22,6 @@ export default {
dataSource,
pageInfo,
onPageChange,
onPageSizeChange,
rowSelection,
handleSelect,
handleSelectAll,
DEFAULT_PAGE_INFO,
@ -30,9 +29,6 @@ export default {
onPageChange: () => {
getData();
},
onPageSizeChange: () => {
getData();
},
});
let queryTaskTimer = null;
@ -75,10 +71,12 @@ export default {
getData();
};
const handleSorterChange = (column, order) => {
query.value.sort_column = column;
query.value.sort_order = order === 'ascend' ? 'asc' : 'desc';
reload();
const handleSorterChange = (pagination, filters, sorter) => {
if (sorter && !Array.isArray(sorter) && sorter.columnKey) {
query.value.sort_column = sorter.columnKey;
query.value.sort_order = sorter.order === 'ascend' ? 'asc' : 'desc';
reload();
}
};
const handleSearch = () => {
@ -137,10 +135,10 @@ export default {
completeTaskNum++;
const notificationId = downloadTaskInfos.value.find((v) => v.id === id)?.randomId;
notificationId && Notification.remove(notificationId);
notificationId && notification.close(notificationId);
if (status === 1) {
AMessage.success('批量下载已完成,正在下载文件...');
message.success('批量下载已完成,正在下载文件...');
downloadByUrl(file);
} else if (status === 2) {
const onReDownload = () => {
@ -208,11 +206,11 @@ export default {
<div class="filter-row-item flex items-center">
<span class="label">操作人员</span>
<Input
v-model={query.value.operator_name}
v-model:value={query.value.operator_name}
class="w-240px"
placeholder="请输入操作人员"
size="medium"
allow-clear
size="middle"
allowClear
onChange={handleSearch}
v-slots={{
prefix: () => <IconSearch />,
@ -222,11 +220,11 @@ export default {
<div class="filter-row-item flex items-center">
<span class="label">所属模块</span>
<Input
v-model={query.value.module}
v-model:value={query.value.module}
class="w-240px"
placeholder="请输入所属模块"
size="medium"
allow-clear
size="middle"
allowClear
onChange={handleSearch}
v-slots={{
prefix: () => <IconSearch />,
@ -239,17 +237,17 @@ export default {
{dataSource.value.length > 0 && selectedRows.value.length > 0 && (
<div
class={[
'tip-row flex justify-between px-16px py-10px w-100% mb-16px h-42px',
'tip-row flex justify-between px-16px py-10px w-100% mb-16px h-48px items-center',
selectedRows.value.length > 0 ? ' selected' : '',
].join('')}
>
<div class="flex items-center">
<div class="flex items-center">
<Checkbox
modelValue={checkedAll.value}
checked={checkedAll.value}
indeterminate={indeterminate.value}
class="mr-8px"
onChange={handleSelectAll}
onChange={(e) => handleSelectAll(e.target.checked)}
/>
<span class="label mr-24px">
已选
@ -271,104 +269,96 @@ export default {
{/* 表格 */}
<Table
ref="tableRef"
data={dataSource.value}
column-resizable
row-key="id"
row-selection={rowSelection.value}
selected-keys={selectedRowKeys.value}
dataSource={dataSource.value}
rowKey="id"
rowSelection={{
selectedRowKeys: selectedRowKeys.value,
onSelect: handleSelect,
onSelectAll: handleSelectAll,
}}
pagination={false}
scroll={{ x: '100%', y: '100%' }}
class="w-100% flex-1 overflow-hidden"
bordered
onSorterChange={handleSorterChange}
onSelect={handleSelect}
onSelectAll={handleSelectAll}
showSorterTooltip={false}
onChange={handleSorterChange}
v-slots={{
empty: () => <NoData />,
columns: () => (
<>
{TABLE_COLUMNS.map((column) => (
<TableColumn
key={column.dataIndex}
data-index={column.dataIndex}
fixed={column.fixed}
width={column.width}
min-width={column.minWidth}
sortable={column.sortable}
align={column.align}
ellipsis
tooltip
v-slots={{
title: () => (
<div class="flex items-center">
<span class="cts mr-4px">{column.title}</span>
{column.tooltip && (
<Tooltip content={column.tooltip} position="top">
<IconQuestionCircle class="tooltip-icon color-#737478" size={16} />
</Tooltip>
)}
</div>
),
cell: ({ record }) => {
if (column.dataIndex === 'status') {
return (
<div class={['status-box', `status-box-${record.status}`]}>
<span>{EXPORT_TASK_STATUS.find((v) => v.value === record.status)?.label}</span>
</div>
);
} else if (column.dataIndex === 'operator.name') {
return record.operator?.name || record.operator?.mobile;
} else if (column.dataIndex === 'created_at') {
return exactFormatTime(record.created_at, 'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss');
} else {
return formatTableField(column, record, true);
}
},
}}
/>
))}
<TableColumn
data-index="operation"
width={dataSource.value.some((record) => record.status !== enumTaskStatus.Exporting) ? 120 : 60}
fixed="right"
title="操作"
v-slots={{
cell: ({ record }) => (
<div class="flex items-center">
<img
src={icon1}
width="14"
height="14"
class="mr-8px cursor-pointer"
onClick={() => handleDelete(record)}
/>
{record.status !== enumTaskStatus.Exporting && (
<Button type="outline" size="mini" class="search-btn" onClick={() => handleDownload(record)}>
{record.status === enumTaskStatus.Failed ? '重新导出' : '下载'}
</Button>
)}
</div>
),
}}
/>
</>
),
emptyText: () => <NoData />,
}}
/>
>
{TABLE_COLUMNS.map((column) => (
<Table.Column
key={column.dataIndex}
dataIndex={column.dataIndex}
fixed={column.fixed}
width={column.width}
minWidth={column.minWidth}
sorter={column.sortable}
align={column.align}
ellipsis
title={() => (
<>
<span class="cts mr-4px">{column.title}</span>
{column.tooltip && (
<Tooltip title={column.tooltip} placement="top">
<IconQuestionCircle class="tooltip-icon color-#737478" size={16} />
</Tooltip>
)}
</>
)}
customRender={({ record }) => {
if (column.dataIndex === 'status') {
return (
<div class={['status-box', `status-box-${record.status}`]}>
<span>{EXPORT_TASK_STATUS.find((v) => v.value === record.status)?.label}</span>
</div>
);
} else if (column.dataIndex === 'operator.name') {
return record.operator?.name || record.operator?.mobile;
} else if (column.dataIndex === 'created_at') {
return exactFormatTime(record.created_at, 'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss');
} else {
return formatTableField(column, record, true);
}
}}
/>
))}
<Table.Column
dataIndex="operation"
width={dataSource.value.some((record) => record.status !== enumTaskStatus.Exporting) ? 120 : 60}
fixed="right"
title="操作"
customRender={({ record }) => (
<div class="flex items-center">
<img
src={icon1}
width="14"
height="14"
class="mr-8px cursor-pointer"
onClick={() => handleDelete(record)}
/>
{record.status !== enumTaskStatus.Exporting && (
<Button type="primary" ghost size="small" class="search-btn" onClick={() => handleDownload(record)}>
{record.status === enumTaskStatus.Failed ? '重新导出' : '下载'}
</Button>
)}
</div>
)}
/>
</Table>
{/* 分页 */}
{pageInfo.value.total > 0 && (
<div class="flex justify-end my-16px">
<Pagination
total={pageInfo.value.total}
size="mini"
show-total
show-jumper
show-page-size
size="small"
showTotal={(total, range) => `${total} 条记录`}
showQuickJumper
showSizeChanger
current={pageInfo.value.page}
page-size={pageInfo.value.page_size}
pageSize={pageInfo.value.page_size}
onChange={onPageChange}
onPageSizeChange={onPageSizeChange}
/>
</div>
)}

View File

@ -1,25 +1,25 @@
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
:title="isBatch ? '批量删除导入记录' : '删除导入记录'"
width="400px"
@close="onClose"
centered
@cancel="onClose"
>
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认删除 {{ accountName }} 这条记录吗</span>
</div>
<template #footer>
<a-button size="large" @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px !bg-#f64b31 !border-none" status="danger" size="large" @click="onDelete"
>确定</a-button
>
<Button @click="onClose">取消</Button>
<Button type="primary" danger @click="onDelete">确定</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Modal, Button, message } from 'ant-design-vue';
import { deleteTask, deleteBatchTasks } from '@/api/all/common';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
@ -51,7 +51,7 @@ async function onDelete() {
const _params = isBatch.value ? { ids: taskId.value } : taskId.value;
const { code } = await _fn(_params);
if (code === 200) {
AMessage.success('删除成功');
message.success('删除成功');
isBatch.value ? emits('batchUpdate') : emits('update');
onClose();
}

View File

@ -1,6 +1,6 @@
<script lang="jsx">
import { ref, computed } from 'vue';
import { Input, Table, TableColumn, Checkbox, Pagination, Button, Tooltip, Notification } from '@arco-design/web-vue';
import { Button, Tooltip, Table, Pagination } from 'ant-design-vue';
import { IconSearch, IconClose, IconQuestionCircle } from '@arco-design/web-vue/es/icon';
import NoData from '@/components/no-data';
import { getTask } from '@/api/all/common';
@ -21,8 +21,6 @@ export default {
dataSource,
pageInfo,
onPageChange,
onPageSizeChange,
rowSelection,
handleSelect,
handleSelectAll,
DEFAULT_PAGE_INFO,
@ -30,9 +28,6 @@ export default {
onPageChange: () => {
getData();
},
onPageSizeChange: () => {
getData();
},
});
const query = ref({ ...INITIAL_FORM });
@ -72,10 +67,12 @@ export default {
getData();
};
const handleSorterChange = (column, order) => {
query.value.sort_column = column;
query.value.sort_order = order === 'ascend' ? 'asc' : 'desc';
reload();
const handleSorterChange = (pagination, filters, sorter) => {
if (sorter && sorter.columnKey) {
query.value.sort_column = sorter.columnKey;
query.value.sort_order = sorter.order === 'ascend' ? 'asc' : 'desc';
reload();
}
};
const handleSearch = () => {
@ -118,171 +115,98 @@ export default {
return () => (
<div class="import-task-wrap">
{/* 筛选行 */}
{/* <div class="filter-row flex mb-16px">
<div class="filter-row-item flex items-center">
<span class="label">操作人员</span>
<Input
v-model={query.value.operator_name}
class="w-240px"
placeholder="请输入操作人员"
size="medium"
allow-clear
onChange={handleSearch}
v-slots={{
prefix: () => <IconSearch />,
}}
/>
</div>
<div class="filter-row-item flex items-center">
<span class="label">所属模块</span>
<Input
v-model={query.value.module}
class="w-240px"
placeholder="请输入所属模块"
size="medium"
allow-clear
onChange={handleSearch}
v-slots={{
prefix: () => <IconSearch />,
}}
/>
</div>
</div> */}
{/* 已选提示行 */}
{/* {dataSource.value.length > 0 && selectedRows.value.length > 0 && (
<div
class={[
'tip-row flex justify-between px-16px py-10px w-100% mb-16px h-42px',
selectedRows.value.length > 0 ? ' selected' : '',
].join('')}
>
<div class="flex items-center">
<div class="flex items-center">
<Checkbox
modelValue={checkedAll.value}
indeterminate={indeterminate.value}
class="mr-8px"
onChange={handleSelectAll}
/>
<span class="label mr-24px">
已选
<span class="color-#6D4CFE">{selectedRows.value.length}</span>
个文件
</span>
<span class="operation-btn" onClick={handleBatchDownload}>
批量下载
</span>
<span class="operation-btn red" onClick={handleBatchDelete}>
批量删除
</span>
</div>
</div>
<IconClose size={16} class="cursor-pointer color-#737478" onClick={handleCloseTip} />
</div>
)} */}
{/* 表格 */}
<Table
ref="tableRef"
data={dataSource.value}
column-resizable
row-key="id"
selected-keys={selectedRowKeys.value}
dataSource={dataSource.value}
rowKey="id"
rowSelection={{
selectedRowKeys: selectedRowKeys.value,
onSelect: handleSelect,
onSelectAll: handleSelectAll,
}}
pagination={false}
scroll={{ x: '100%', y: '100%' }}
class="w-100% flex-1 overflow-hidden"
bordered
onSorterChange={handleSorterChange}
onSelect={handleSelect}
onSelectAll={handleSelectAll}
showSorterTooltip={false}
onChange={handleSorterChange}
v-slots={{
empty: () => <NoData />,
columns: () => (
<>
{TABLE_COLUMNS.map((column) => (
<TableColumn
key={column.dataIndex}
data-index={column.dataIndex}
fixed={column.fixed}
width={column.width}
min-width={column.minWidth}
sortable={column.sortable}
align={column.align}
ellipsis
tooltip
v-slots={{
title: () => (
<div class="flex items-center">
<span class="cts mr-4px">{column.title}</span>
{column.tooltip && (
<Tooltip content={column.tooltip} position="top">
<IconQuestionCircle class="tooltip-icon color-#737478" size={16} />
</Tooltip>
)}
</div>
),
cell: ({ record }) => {
if (column.dataIndex === 'status') {
return (
<div class={['status-box', `status-box-${record.status}`]}>
<span>{IMPORT_TASK_STATUS.find((v) => v.value === record.status)?.label}</span>
</div>
);
} else if (column.dataIndex === 'operator.name') {
return <span>{record.operator?.name || record.operator?.mobile}</span>;
} else if (column.dataIndex === 'created_at') {
return exactFormatTime(record.created_at, 'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss');
} else {
return formatTableField(column, record, true);
}
},
}}
/>
))}
<TableColumn
data-index="operation"
width={dataSource.value.some((record) => record.status === enumTaskStatus.Failed) ? 180 : 60}
fixed="right"
title="操作"
v-slots={{
cell: ({ record }) => (
<div class="flex items-center">
<img
src={icon1}
width="14"
height="14"
class="mr-8px cursor-pointer"
onClick={() => handleDelete(record)}
/>
{record.status === enumTaskStatus.Failed && (
<Button type="outline" size="mini" class="search-btn" onClick={() => handleDownload(record)}>
下载问题表格
</Button>
)}
</div>
),
}}
/>
</>
),
}}
/>
>
{TABLE_COLUMNS.map((column) => (
<Table.Column
key={column.dataIndex}
dataIndex={column.dataIndex}
fixed={column.fixed}
width={column.width}
minWidth={column.minWidth}
sorter={column.sortable}
align={column.align}
ellipsis
title={() => (
<>
<span class="cts mr-4px">{column.title}</span>
{column.tooltip && (
<Tooltip title={column.tooltip} placement="top">
<IconQuestionCircle class="tooltip-icon color-#737478" size={16} />
</Tooltip>
)}
</>
)}
customRender={({ record }) => {
if (column.dataIndex === 'status') {
return (
<div class={['status-box', `status-box-${record.status}`]}>
<span>{IMPORT_TASK_STATUS.find((v) => v.value === record.status)?.label}</span>
</div>
);
} else if (column.dataIndex === 'operator.name') {
return <span>{record.operator?.name || record.operator?.mobile}</span>;
} else if (column.dataIndex === 'created_at') {
return exactFormatTime(record.created_at, 'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss');
} else {
return formatTableField(column, record, true);
}
}}
/>
))}
<Table.Column
dataIndex="operation"
width={dataSource.value.some((record) => record.status === enumTaskStatus.Failed) ? 180 : 60}
fixed="right"
title="操作"
customRender={({ record }) => (
<div class="flex items-center">
<img
src={icon1}
width="14"
height="14"
class="mr-8px cursor-pointer"
onClick={() => handleDelete(record)}
/>
{record.status === enumTaskStatus.Failed && (
<Button type="primary" ghost size="small" class="search-btn" onClick={() => handleDownload(record)}>
下载问题表格
</Button>
)}
</div>
)}
/>
</Table>
{/* 分页 */}
{pageInfo.value.total > 0 && (
<div class="flex justify-end my-16px">
<Pagination
total={pageInfo.value.total}
size="mini"
show-total
show-jumper
show-page-size
size="small"
showTotal={(total, range) => `${total} 条记录`}
showQuickJumper
showSizeChanger
current={pageInfo.value.page}
page-size={pageInfo.value.page_size}
pageSize={pageInfo.value.page_size}
onChange={onPageChange}
onPageSizeChange={onPageSizeChange}
/>
</div>
)}

View File

@ -1,37 +1,39 @@
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
title="任务中心"
modal-class="task-center-modal"
wrapClassName="task-center-modal"
width="860px"
:mask-closable="false"
:footer="false"
@close="onClose"
:footer="null"
@cancel="onClose"
centered
>
<a-tabs :active-key="activeTab" @tab-click="handleTabClick">
<a-tab-pane key="0" title="导入"> </a-tab-pane>
<a-tab-pane key="1" title="导出"> </a-tab-pane>
</a-tabs>
<Tabs v-model:activeKey="activeTab" @change="handleTabClick">
<TabPane key="1" tab="导入"> </TabPane>
<TabPane key="2" tab="导出"> </TabPane>
</Tabs>
<div class="content">
<component :is="activeTab === '0' ? ImportTask : ExportTask" ref="componentRef" />
<component :is="activeTab === '1' ? ImportTask : ExportTask" ref="componentRef" />
</div>
</a-modal>
</Modal>
</template>
<script setup>
import { Notification } from '@arco-design/web-vue';
import { Checkbox, Modal, Button, Tabs, notification } from 'ant-design-vue';
const { TabPane } = Tabs;
import ExportTask from './components/export-task';
import ImportTask from './components/import-task';
const visible = ref(false);
const componentRef = ref(null);
const activeTab = ref('0');
const activeTab = ref('1');
let timer = null;
const handleTabClick = (key) => {
activeTab.value = key;
// activeTab.value = key;
nextTick(() => {
getData();
});
@ -42,20 +44,21 @@ const getData = () => {
};
const open = () => {
getData();
visible.value = true;
nextTick(() => {
getData();
});
timer = setInterval(() => {
getData();
}, 10000);
visible.value = true;
};
const onClose = () => {
activeTab.value = '0';
clearTimer();
componentRef.value?.unloadComp?.();
Notification.clear();
notification.destroy();
visible.value = false;
};
const clearTimer = () => {

View File

@ -1,7 +1,7 @@
.task-center-modal {
.arco-modal-header {
.ant-modal-header {
border-bottom: none !important;
.arco-modal-title {
.ant-modal-title {
color: var(--Text-1, #211f24);
font-size: 16px;
font-style: normal;
@ -10,7 +10,7 @@
font-family: $font-family-medium;
}
}
.arco-modal-body {
.ant-modal-body {
padding: 0 !important;
display: flex;
flex-direction: column;

View File

@ -7,17 +7,21 @@ import router from './router';
import store from './stores';
import * as directives from '@/directives';
import VueLazyLoad from "vue-lazyload";
import NoData from '@/components/no-data/index.vue';
import SvgIcon from '@/components/svg-icon/index.vue';
import '@/api/index';
import '@arco-design/web-vue/dist/arco.css'; // Arco 默认样式
import './core';
import '@arco-design/web-vue/dist/arco.css'; // 已移除 Arco 样式
import 'normalize.css';
import 'uno.css';
import 'virtual:svg-icons-register';
import errorImage from '@/assets/img/error-img.png';
import loadingImage from '@/assets/img/error-img.png';
// import '@/styles/vars.css'; // 优先加载
const app = createApp(App);
@ -27,6 +31,10 @@ app.component('SvgIcon', SvgIcon);
app.use(store);
app.use(router);
app.use(VueLazyLoad, {
error: errorImage,
loading: loadingImage,
});
Object.values(directives).forEach((directive) => {
app.use(directive);

View File

@ -3,6 +3,7 @@
* @Date: 2025-06-22 22:59:16
*/
import type { Router } from 'vue-router';
import { message } from 'ant-design-vue';
import NProgress from 'nprogress';
import { goUserLogin } from '@/utils/user';
// import router from '@/router';
@ -29,7 +30,7 @@ export default function setupUserLoginInfoGuard(router: Router) {
// if (requiresAuth) {
// const hasPermission = checkRoutePermission(routeName);
// if (!hasPermission) {
// AMessage.error('您没有权限访问该页面');
// message.error('您没有权限访问该页面');
// next('/');
// return;
// }

View File

@ -1,89 +1,89 @@
// import { IconBookmark } from '@arco-design/web-vue/es/icon';
// import type { AppRouteRecordRaw } from '../types';
// import { MENU_GROUP_IDS } from '@/router/constants';
import { IconBookmark } from '@arco-design/web-vue/es/icon';
import type { AppRouteRecordRaw } from '../types';
import { MENU_GROUP_IDS } from '@/router/constants';
// const COMPONENTS: AppRouteRecordRaw[] = [
// {
// path: '/dataEngine',
// name: 'DataEngine',
// redirect: 'dataEngine/hotTranslation',
// meta: {
// locale: '全域数据引擎',
// icon: IconBookmark,
// requiresAuth: true,
// requireLogin: true,
// roles: ['*'],
// id: MENU_GROUP_IDS.DATA_ENGINE_ID,
// },
// children: [
// {
// path: 'hotTranslation',
// name: 'DataEngineHotTranslation',
// meta: {
// locale: '行业热门话题洞察',
// requiresAuth: true,
// requireLogin: true,
// roles: ['*'],
// },
// component: () => import('@/views/components/dataEngine/hotTranslation.vue'),
// },
// {
// path: 'hotCloud',
// name: 'DataEngineHotCloud',
// meta: {
// locale: '行业词云',
// requiresAuth: true,
// requireLogin: true,
// roles: ['*'],
// },
// component: () => import('@/views/components/dataEngine/hotCloud.vue'),
// },
// {
// path: 'keyWord',
// name: 'DataEngineKeyWord',
// meta: {
// locale: '行业关键词动向',
// requiresAuth: true,
// requireLogin: true,
// roles: ['*'],
// },
// component: () => import('@/views/components/dataEngine/keyWord.vue'),
// },
// {
// path: 'userPainPoints',
// name: 'DataEngineUserPainPoints',
// meta: {
// locale: '用户痛点观察',
// requiresAuth: true,
// requireLogin: true,
// roles: ['*'],
// },
// component: () => import('@/views/components/dataEngine/userPainPoints.vue'),
// },
// {
// path: 'keyBrandMovement',
// name: 'DataEngineKeyBrandMovement',
// meta: {
// locale: '重点品牌动向',
// requiresAuth: true,
// requireLogin: true,
// roles: ['*'],
// },
// component: () => import('@/views/components/dataEngine/keyBrandMovement.vue'),
// },
// {
// path: 'userPersona',
// name: 'DataEngineUserPersona',
// meta: {
// locale: '用户画像',
// requiresAuth: true,
// requireLogin: true,
// roles: ['*'],
// },
// component: () => import('@/views/components/dataEngine/userPersona.vue'),
// },
// ],
// },
// ];
const COMPONENTS: AppRouteRecordRaw[] = [
{
path: '/dataEngine',
name: 'DataEngine',
redirect: 'dataEngine/hotTranslation',
meta: {
locale: '全域数据引擎',
icon: IconBookmark,
requiresAuth: true,
requireLogin: true,
roles: ['*'],
id: MENU_GROUP_IDS.DATA_ENGINE_ID,
},
children: [
{
path: 'hotTranslation',
name: 'DataEngineHotTranslation',
meta: {
locale: '行业热门话题洞察',
requiresAuth: true,
requireLogin: true,
roles: ['*'],
},
component: () => import('@/views/components/dataEngine/hotTranslation.vue'),
},
{
path: 'hotCloud',
name: 'DataEngineHotCloud',
meta: {
locale: '行业词云',
requiresAuth: true,
requireLogin: true,
roles: ['*'],
},
component: () => import('@/views/components/dataEngine/hotCloud.vue'),
},
{
path: 'keyWord',
name: 'DataEngineKeyWord',
meta: {
locale: '行业关键词动向',
requiresAuth: true,
requireLogin: true,
roles: ['*'],
},
component: () => import('@/views/components/dataEngine/keyWord.vue'),
},
{
path: 'userPainPoints',
name: 'DataEngineUserPainPoints',
meta: {
locale: '用户痛点观察',
requiresAuth: true,
requireLogin: true,
roles: ['*'],
},
component: () => import('@/views/components/dataEngine/userPainPoints.vue'),
},
{
path: 'keyBrandMovement',
name: 'DataEngineKeyBrandMovement',
meta: {
locale: '重点品牌动向',
requiresAuth: true,
requireLogin: true,
roles: ['*'],
},
component: () => import('@/views/components/dataEngine/keyBrandMovement.vue'),
},
{
path: 'userPersona',
name: 'DataEngineUserPersona',
meta: {
locale: '用户画像',
requiresAuth: true,
requireLogin: true,
roles: ['*'],
},
component: () => import('@/views/components/dataEngine/userPersona.vue'),
},
],
},
];
// export default COMPONENTS;
export default COMPONENTS;

View File

@ -7,9 +7,9 @@ import { MENU_GROUP_IDS } from '@/router/constants';
import IconRepository from '@/assets/svg/svg-repository.svg';
import IconMediaAccount from '@/assets/svg/svg-mediaAccount.svg';
import IconPutAccount from '@/assets/svg/svg-putAccount.svg';
import IconIntelligentSolution from '@/assets/svg/svg-intelligentSolution.svg';
import IconProjectManagement from '@/assets/svg/svg-projectManagement.svg';
// import IconPutAccount from '@/assets/svg/svg-putAccount.svg';
// import IconIntelligentSolution from '@/assets/svg/svg-intelligentSolution.svg';
// import IconProjectManagement from '@/assets/svg/svg-projectManagement.svg';
const COMPONENTS: AppRouteRecordRaw[] = [
{
@ -196,32 +196,32 @@ const COMPONENTS: AppRouteRecordRaw[] = [
// },
// ],
// },
{
path: '/project-manage',
name: 'ProjectManagement',
redirect: 'project-manage/project-list',
meta: {
locale: '项目管理',
icon: IconProjectManagement,
requiresAuth: true,
requireLogin: true,
roles: ['*'],
id: MENU_GROUP_IDS.PROPERTY_ID,
},
children: [
{
path: 'project-list',
name: 'ProjectList',
meta: {
locale: '项目列表',
requiresAuth: true,
requireLogin: true,
roles: ['*'],
},
component: () => import('@/views/property-marketing/project-manage/project-list/index.vue'),
},
],
},
// {
// path: '/project-manage',
// name: 'ProjectManagement',
// redirect: 'project-manage/project-list',
// meta: {
// locale: '项目管理',
// icon: IconProjectManagement,
// requiresAuth: true,
// requireLogin: true,
// roles: ['*'],
// id: MENU_GROUP_IDS.PROPERTY_ID,
// },
// children: [
// {
// path: 'project-list',
// name: 'ProjectList',
// meta: {
// locale: '项目列表',
// requiresAuth: true,
// requireLogin: true,
// roles: ['*'],
// },
// component: () => import('@/views/property-marketing/project-manage/project-list/index.vue'),
// },
// ],
// },
];
export default COMPONENTS;

View File

@ -3,7 +3,7 @@
* @Date: 2025-06-23 03:56:22
*/
import { defineStore } from 'pinia';
import type { AppState, RouteRecordNormalized, NotificationReturn } from './types';
import type { AppState, RouteRecordNormalized } from './types';
import defaultSettings from '@/config/settings.json';
import type { AppRouteRecordRaw } from '@/router/routes/types';

View File

@ -1,8 +1,7 @@
import type { RouteRecordNormalized } from 'vue-router';
import type { NotificationReturn } from '@arco-design/web-vue/es/notification/interface';
import type { AppRouteRecordRaw } from '@/router/routes/types';
export { RouteRecordNormalized, NotificationReturn };
export { RouteRecordNormalized };
export interface AppState {
theme: string;

View File

@ -1,9 +1,9 @@
.arco-modal {
.arco-modal-header {
.ant-modal {
.ant-modal-header {
border-bottom: 1px solid var(--Border-1, #d7d7d9);
height: 56px;
padding: 0 20px;
.arco-modal-title {
.ant-modal-title {
font-family: $font-family-medium;
color: #211f24;
font-size: 16px;
@ -13,11 +13,11 @@
}
}
.arco-modal-body {
.ant-modal-body {
padding: 24px 20px;
}
.arco-modal-footer {
.ant-modal-footer {
display: flex;
height: 64px;
padding: 0px 20px;

View File

@ -1,4 +1,4 @@
import { Notification } from '@arco-design/web-vue';
import { notification } from 'ant-design-vue';
import { downloadByUrl } from '@/utils/tools';
import { IconLoading } from '@arco-design/web-vue/es/icon';
@ -16,30 +16,30 @@ interface RenderNotificationData {
// 下载通知框
export function showExportNotification(label: string, others: { id?: string, duration?: number }) {
const { id = '', duration = 3000 } = others ?? {}
Notification.warning({
id,
showIcon: false,
closable: true,
content: () => (
const { id = '', duration = 3 } = others ?? {}
notification.warning({
key: id,
icon: () => null,
message: () => null,
description: (
<div class="flex items-center pr-16px">
<IconLoading size={16} class="color-#6D4CFE mr-8px" />
<p class="text-14px lh-22px font-400 color-#211F24">{label}</p>
</div>
),
duration,
class: 'px-16px py-9px w-450px rounded-2px bg-#F0EDFF',
class: 'w-450px rounded-2px bg-#F0EDFF',
});
}
// 下载失败框
export function showFailExportNotification(label: string, others: { id?: string, duration?: number, onReDownload?: Function }) {
const { id = '', duration = 0, onReDownload } = others ?? {}
Notification.warning({
id,
showIcon: false,
closable: true,
content: () => (
notification.warning({
key: id,
icon: () => null,
message: () => null,
description: (
<div class="flex items-center justify-between pr-16px">
<div class="flex items-center mr-10px">
<img src={icon3} width={16} height={16} class=" mr-8px" />
@ -50,7 +50,7 @@ export function showFailExportNotification(label: string, others: { id?: string,
</div>
),
duration,
class: 'px-16px py-9px w-500px rounded-2px bg-#FFE9E7',
class: 'w-500px rounded-2px bg-#FFE9E7',
});
}
@ -61,10 +61,10 @@ export const showImportResultNotification = (data: RenderNotificationData) => {
file && downloadByUrl(file);
};
Notification.warning({
showIcon: false,
closable: true,
content: () => (
notification.warning({
icon: () => null,
message: () => null,
description: (
<div>
<div class="flex items-center mb-4px">
<img src={hasError ? icon1 : icon2} width="16" height="16" class="mr-8px" />
@ -89,6 +89,6 @@ export const showImportResultNotification = (data: RenderNotificationData) => {
</div>
),
duration: 3000,
class: `px-16px py-16px w-400px rounded-2px ${hasError ? 'bg-#FFF7E5' : 'bg-#EBF7F2'}`,
class: `w-400px rounded-2px ${hasError ? 'bg-#FFF7E5' : 'bg-#EBF7F2'}`,
});
};

View File

@ -7,9 +7,9 @@
</div>
<div class="info-section">
<div class="title-group">
<a-tooltip :content="cozeInfo.name">
<Tooltip :title="cozeInfo.name">
<div class="title">{{ cozeInfo.name }}</div>
</a-tooltip>
</Tooltip>
<div class="tag">
<div>
<img class="status-icon" :src="chatbotIcon" />
@ -18,12 +18,8 @@
</div>
</div>
<div class="usage-info">
<a-space>
<span class="count">{{ cozeInfo.views }}</span>
</a-space>
<a-space>
<span class="label"> 次使用 </span>
</a-space>
<span class="count">{{ cozeInfo.views }}</span>
<span class="label"> 次使用 </span>
</div>
</div>
<div class="description-section">
@ -36,6 +32,7 @@
<script lang="ts" setup>
import { defineProps } from 'vue';
import { Tooltip } from 'ant-design-vue';
import chatbotIcon from '@/assets/svg/chatbot.svg';
const props = defineProps({

View File

@ -36,9 +36,9 @@
<div class="body">
<div class="">
<div class="toggle-btn cursor-pointer" @click="toggleCollapse">
<a-tooltip :content="isCollapsed ? '展开' : '折叠'">
<Tooltip :title="isCollapsed ? '展开' : '折叠'">
<img class="status-icon" :src="isCollapsed ? menuUnfold : menuFold" />
</a-tooltip>
</Tooltip>
</div>
</div>
</div>
@ -55,6 +55,7 @@ import { getChatAgent } from '@/api/all/agent';
import { useRouter } from 'vue-router';
import menuFold from '@/assets/svg/menu-fold.svg';
import menuUnfold from '@/assets/svg/menu-unfold.svg';
import { Tooltip } from 'ant-design-vue';
import { formatNumberShow } from '@/utils/tools';
const router = useRouter();

View File

@ -1,41 +1,42 @@
<template>
<div class="agent-wrap relative h-full pl-16px">
<a-input
v-model="query.name"
<Input
v-model:value="query.name"
@press-enter="getData()"
placeholder="搜索智能体"
size="large"
allow-clear
class="absolute right-0 top-4px !w-400px"
class="absolute right-0 top-4px !w-400px"
>
<template #prefix>
<icon-search @click="getData()" />
</template>
</a-input>
<div v-for="(item, index) in list" :key="index">
</Input>
<div v-for="(item, index) in list" :key="index">
<p class="span-title w-fit mb-16px">{{ item.name }}</p>
<a-row class="grid-demo" :gutter="[20, 16]" v-if="item.agent_products.length > 0">
<a-col :xs="24"
:sm="12"
:md="8"
:lg="5"
:xl="6"
:xxl="4"
v-for="(product, k) in item.agent_products" :key="k">
<Row class="grid-demo" :gutter="[20, 16]" v-if="item.agent_products.length > 0">
<Col :xs="24" :sm="12" :md="8" :lg="5" :xl="6" :xxl="4" v-for="(product, k) in item.agent_products" :key="k">
<div class="card-container cursor-pointer !h-252px" @click="goDetail(product?.type, product?.id)">
<div class="card-image h-120px w-100% bg-cover bg-center mb-8px" v-image-main-color="product.image_url">
<img class="object-contain h-full w-100% " :src="product?.image_url"/>
<div class="card-image h-120px w-100% bg-cover bg-center mb-8px" v-image-main-color="product.image_url">
<img class="object-contain h-full w-100%" :src="product?.image_url" />
</div>
<div class="card-content w-full">
<TextoverTips :context="product.name" class="card-title mb-4px !text-16px"/>
<TextoverTips :context="product.description" class="card-description mb-8px color-#737478 text-14px lh-22px font-400" :line="2" />
<TextOverTips :context="product.name" class="card-title mb-4px !text-16px" />
<TextOverTips
:context="product.description"
class="card-description mb-8px color-#737478 text-14px lh-22px font-400"
:line="2"
/>
</div>
<div class="card-footer">
<div
:class="['status-tag', product.type === 1 ? 'blue-tag' : 'red-tag']"
:style="{ background: product.type === 1 ? 'var(--Functional-Blue-1, #F0EDFF)' : 'var(--Functional-Red-1, #FFE9E7)' }"
:style="{
background:
product.type === 1 ? 'var(--Functional-Blue-1, #F0EDFF)' : 'var(--Functional-Red-1, #FFE9E7)',
}"
data-size="mini-20px"
>
<SvgIcon
@ -48,13 +49,15 @@
<div class="status-text">{{ product.type === 1 ? '对话式' : '工作流' }}</div>
</div>
<div class="usage-info">
<div class="usage-count mr-2px">{{ formatNumberShow({ value: product?.views, showExactValue: true }) }}</div>
<div class="usage-count mr-2px">
{{ formatNumberShow({ value: product?.views, showExactValue: true }) }}
</div>
<div class="usage-label">次使用</div>
</div>
</div>
</div>
</a-col>
</a-row>
</Col>
</Row>
<NoData v-else />
</div>
@ -62,10 +65,11 @@
</template>
<script setup lang="ts">
import { Input, Row, Col } from 'ant-design-vue';
import { useRouter } from 'vue-router';
import { getAgentList } from '@/api/all/agent';
import { formatNumberShow } from "@/utils/tools";
import TextoverTips from "@/components/text-over-tips";
import { formatNumberShow } from '@/utils/tools';
import TextOverTips from '@/components/text-over-tips';
const router = useRouter();

View File

@ -1,29 +1,26 @@
<template>
<div class="form-container">
<a-form :model="formData" ref="formRef" layout="vertical">
<a-form-item
<Form :model="formData" ref="formRef" layout="vertical">
<FormItem
v-for="(field, index) in formFields"
:key="index"
:label="field.props.label"
:field="field.props.name"
:name="field.props.name"
:rules="field.props.rules"
:tooltip="field.props.tip"
>
<a-input
<Input
allowClear
v-if="field.type === 'input'"
v-model="formData[field.props.name]"
v-model:value="formData[field.props.name]"
:placeholder="field?.props?.placeholder"
/>
<a-textarea
<TextArea
v-if="field.type === 'textarea'"
style="width: 500px; height: 200px"
v-model="formData[field.props.name]"
v-model:value="formData[field.props.name]"
:placeholder="field?.props?.placeholder"
/>
<!-- <a-color-picker v-if="field.type === 'color_picker'"
style="width: 500px; height: 200px"
v-model="formData[field.props.name]" /> -->
<ImageUpload
v-if="field.type == 'upload_image'"
v-model="formData[field.props.name]"
@ -34,24 +31,28 @@
v-model="formData[field.props.name]"
:limit="field.props.limit"
></FileUpload>
<a-select
<Select
v-else-if="field.type === 'select'"
v-model="formData[field.props.name]"
v-model:value="formData[field.props.name]"
:placeholder="field.placeholder"
>
<a-option v-for="(option, optIndex) in field.props.options" :key="optIndex" :value="option.value">
<Option v-for="(option, optIndex) in field.props.options" :key="optIndex" :value="option.value">
{{ option.label }}
</a-option>
</a-select>
</a-form-item>
</a-form>
<a-button class="submit-btn" type="primary" :disabled="loading" @click="handleSubmit">提交执行</a-button>
</Option>
</Select>
</FormItem>
</Form>
<Button class="submit-btn" type="primary" :disabled="loading" @click="handleSubmit">提交执行</Button>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
import ImageUpload from '@/components/upload/ImageUpload.vue';
import FileUpload from '@/components/upload/FileUpload.vue';
import { Button, Input, Select, Row, Col, Form } from 'ant-design-vue';
const { TextArea } = Input;
const { Option } = Select;
const { Item: FormItem } = Form;
const props = defineProps({
formFields: {
@ -79,24 +80,25 @@ const handleSubmit = async () => {
<style scoped lang="scss">
.form-container {
:deep(.arco-input-wrapper),
:deep(.arco-textarea-wrapper) {
:deep(.ant-input),
:deep(.ant-input:focus),
:deep(.ant-input-focused) {
border-radius: 4px;
border-color: #d7d7d9;
background-color: #fff;
height: 35px;
width: 300px;
&:focus-within,
&.arco-input-focus {
&:focus,
&:focus-within {
background-color: var(--color-bg-2);
border-color: rgb(var(--primary-6));
box-shadow: 0 0 0 0 var(--color-primary-light-2);
}
}
&.arco-textarea-wrapper {
height: 60px;
}
&.ant-textarea-wrapper {
height: 60px;
}
.submit-btn {

View File

@ -42,15 +42,14 @@
{{ item.title }}
</div>
<div class="trigger-container">
<a-trigger
mouse-leave-delay="200"
position="top"
<Dropdown
:mouseLeaveDelay="200"
placement="top"
trigger="hover"
:auto-fit-position="false"
:unmount-on-close="true"
:overlayStyle="{ width: 'auto' }"
>
<SvgIcon size="12" name="svg-more" class="icon-more" />
<template #content>
<template #overlay>
<div class="">
<div class="history-item-dropdown">
<div class="dropdown-item">
@ -61,18 +60,19 @@
</div>
<div class="dropdown-item">
<SvgIcon size="12" name="svg-delete" class="icon color-#6D4CFE" />
<a-popconfirm
<Popconfirm
content="你确认删除该历史对话吗"
@ok="deleteHistory(item.id, index)"
type="error"
@confirm="deleteHistory(item.id, index)"
ok-text="确定"
cancel-text="取消"
>
<div class="text delete">删除</div>
</a-popconfirm>
</Popconfirm>
</div>
</div>
</div>
</template>
</a-trigger>
</Dropdown>
</div>
</div>
</div>
@ -83,9 +83,9 @@
<div class="body">
<div class="">
<div class="toggle-btn cursor-pointer" @click="toggleCollapse">
<a-tooltip :content="isCollapsed ? '展开' : '折叠'">
<Tooltip :title="isCollapsed ? '展开' : '折叠'">
<img class="status-icon" :src="isCollapsed ? menuUnfold : menuFold" />
</a-tooltip>
</Tooltip>
</div>
</div>
</div>
@ -95,7 +95,7 @@
<DynamicForm :formFields="formFields.form" :formData="formData" :loading="loading" @submit="handleSubmit" />
</div>
<div class="res h-full">
<a-spin v-if="loading" class="spin-center" tip="生成中。。。" />
<Spin v-if="loading" wrapperClassName="spin-center" tip="生成中。。。" />
<div
class="markdown-container"
v-if="workFlowRes.output != '' && loading === false"
@ -107,15 +107,16 @@
</div>
</div>
<a-modal style="width: 500px" v-model:visible="editHistoryVisible">
<Modal style="width: 500px" v-model:open="editHistoryVisible" centered>
<template #title> Title</template>
<div></div>
</a-modal>
</Modal>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { Modal, Tooltip, message, Popconfirm, Spin, Dropdown } from 'ant-design-vue';
import DynamicForm from './components/DynamicForm.vue';
import {
executeWorkFlow,
@ -214,17 +215,17 @@ const handleTop = async (id, sort, event) => {
//置顶
const topHistory = async (id, sort) => {
const { code, message } = await topWorkflowHistoryApi(id);
const { code, message: msg } = await topWorkflowHistoryApi(id);
if (code === 200) {
AMessage.success(message);
message.success(msg);
getWorkflowHistoryList();
}
};
//取消置顶
const canceltopHistory = async (id, sort) => {
const { code, message } = await cancelTopWorkflowHistoryApi(id);
const { code, message: msg } = await cancelTopWorkflowHistoryApi(id);
if (code === 200) {
AMessage.success(message);
message.success(msg);
getWorkflowHistoryList();
}
};

View File

@ -1,13 +1,15 @@
<template>
<a-select allow-search v-model="selectedValue" placeholder="请选择账号" allow-clear filterable @change="handleChange">
<a-option v-for="account in filteredAccounts" :key="account.id" :value="account.id" :label="account.name">
<Select showSearch v-model:value="selectedValue" placeholder="请选择账号" allowClear filterable @change="handleChange">
<Option v-for="account in filteredAccounts" :key="account.id" :value="account.id" :label="account.name">
{{ account.name }}
</a-option>
</a-select>
</Option>
</Select>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { Select } from 'ant-design-vue';
const { Option } = Select;
import { ref, onMounted } from 'vue';
import { getPlacementAccountsList } from '@/api/all/propertyMarketing';
// 定义账号对象类型

View File

@ -1,13 +1,15 @@
<template>
<a-select allow-search v-model="selectedValue" placeholder="请选择计划" allow-clear filterable @change="handleChange">
<a-option v-for="item in listData" :key="item.id" :value="item.id" :label="item.name">
<Select showSearch v-model:value="selectedValue" placeholder="请选择计划" allowClear filterable @change="handleChange">
<Option v-for="item in listData" :key="item.id" :value="item.id" :label="item.name">
{{ item.name }}
</a-option>
</a-select>
</Option>
</Select>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { Select } from 'ant-design-vue';
const { Option } = Select;
import { ref, computed, onMounted, type PropType } from 'vue';
import { getplacementAccountProjectsLlist } from '@/api/all/propertyMarketing';
interface Account {
@ -17,7 +19,7 @@ interface Account {
const props = defineProps({
modelValue: {
type: Array,
type: Array as PropType<number[]>,
default: () => [],
},
});
@ -25,7 +27,7 @@ const props = defineProps({
const emit = defineEmits(['update:modelValue', 'change']);
// 响应式数据
const selectedValue = ref(props.modelValue);
const selectedValue = ref<number | undefined>(props.modelValue?.[0]);
const allAccounts = ref<Account[]>([]);
const listData = ref<Account[]>([]);
const loading = ref(false);

View File

@ -1,13 +1,13 @@
<template>
<view>
<topHeader ref="topHeaderRef" @search="search"></topHeader>
<a-space direction="vertical" class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px">
<div class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px">
<div class="title-row">
<span class="title mr-4px">行业词云</span>
<a-tooltip>
<template #content>基于行业内内容提取的高频词汇</template>
<Tooltip>
<template #title>基于行业内内容提取的高频词汇</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<div class="multi-row-tag-cloud h-472px">
@ -20,7 +20,7 @@
class="tag-row"
:style="{ justifyContent: row.align || 'center' }"
>
<a-tag
<Tag
v-for="(tag, tagIndex) in row.tags"
:key="tagIndex"
class="cursor-pointer"
@ -39,16 +39,14 @@
@mouseenter="hoverTag = tag"
@mouseleave="hoverTag = null"
>
<a-space>
<a-tooltip :content="`性价比:${Number(tag.rate * 100)}%`" position="tl">
<a-space>{{ tag.term }}</a-space>
</a-tooltip>
</a-space>
</a-tag>
<Tooltip :title="`性价比:${Number(tag.rate * 100)}%`" placement="topLeft">
<span>{{ tag.term }}</span>
</Tooltip>
</Tag>
</div>
</template>
</div>
</a-space>
</div>
</view>
</template>
@ -56,6 +54,7 @@
import topHeader from './topHeader.vue';
import { ref, computed } from 'vue';
import { fetchindustryTerms } from '@/api/all/index';
import { Tooltip, Tag } from 'ant-design-vue';
const topHeaderRef = ref();
// 从topHeader获取统一的状态
@ -170,7 +169,7 @@ const processTagData = (apiData) => {
color: #6d4cfe !important;
border-color: #6d4cfe !important;
}
:deep(.arco-modal-body) {
:deep(.ant-modal-body) {
padding: 0px;
}
@ -199,7 +198,7 @@ const processTagData = (apiData) => {
}
/* 悬停放大效果 */
a-tag:hover {
.ant-tag:hover {
transform: scale(1.1);
z-index: 1;
}

View File

@ -2,88 +2,99 @@
<view>
<topHeader ref="topHeaderRef" @search="search"></topHeader>
<!-- tabel -->
<a-space
direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px"
>
<div class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px">
<div class="title-row">
<span class="title mr-4px">行业热门话题洞察</span>
<a-tooltip>
<template #content>基于社交内容平台的行业数据分析用户关注的热门话题与趋势</template>
<Tooltip>
<template #title>基于社交内容平台的行业数据分析用户关注的热门话题与趋势</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<a-table
:columns="columns"
:data="dataList"
:filter-icon-align-left="alignLeft"
:scroll="true"
<Table
:dataSource="dataList"
:pagination="false"
:showSorterTooltip="false"
@change="handleChange"
>
<template #empty>
<Table.Column
v-for="column in columns"
:key="column.dataIndex"
:title="column.title"
:dataIndex="column.dataIndex"
:width="column.width"
:minWidth="column.minWidth"
:sortable="column.sortable"
>
<template v-if="column.slotName === 'rank'" #customRender="{ record }">
<img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" />
<span v-else>{{ record.rank }}</span>
</template>
<template v-else-if="column.slotName === 'keywords'" #customRender="{ record }">
<Tag
v-for="item in record.keywords"
:key="item"
class="!rounded-2px !px-8px !py-1px !bg-#F2F3F5 !h-22px !color-#3C4043 mb-5px mr-5px"
>{{ item }}</Tag
>
</template>
<template v-else-if="column.slotName === 'hot'" #customRender="{ record }">
<img
v-for="i in record.hot"
:key="i"
:src="starImages[i - 1]"
style="width: 16px; height: 16px"
class="mr-2px"
/>
</template>
<template v-else-if="column.slotName === 'sentiment'" #customRender="{ record }">
<img v-if="record.felling == '2'" src="@/assets/img/hottranslation/good.png" class="w-24px h-24px" />
<img v-else-if="record.felling == '1'" src="@/assets/img/hottranslation/normal.png" class="w-24px h-24px" />
<img v-else-if="record.felling == '0'" src="@/assets/img/hottranslation/poor.png" class="w-24px h-24px" />
</template>
<template v-else-if="column.slotName === 'optional'" #customRender="{ record }">
<Button type="primary" ghost @click="gotoDetail(record)">详情</Button>
</template>
<template v-else-if="column.titleSlotName === 'hotTitle'" #title>
<div class="flex items-center">
<span class="mr-8px">热度指数</span>
<Tooltip>
<template #title>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template>
<icon-question-circle size="14" class="!color-#737478" />
</Tooltip>
</div>
</template>
<template v-else-if="column.titleSlotName === 'sentimentTitle'" #title>
<div class="flex items-center">
<span class="mr-8px">情感倾向</span>
<Tooltip>
<template #title
>统计该行业下全部内容的情绪分布选取占比最高的情绪类型作为该话题的整体情感倾向</template
>
<icon-question-circle size="14" class="!color-#737478" />
</Tooltip>
</div>
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
<template #hotTitle>
<a-space>
<span>热度指数</span>
<a-tooltip>
<template #content>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template>
<icon-question-circle size="14" class="!color-#737478" />
</a-tooltip>
</a-space>
</template>
<template #sentimentTitle>
<a-space>
<span>情感倾向</span>
<a-tooltip>
<template #content
>统计该行业下全部内容的情绪分布选取占比最高的情绪类型作为该话题的整体情感倾向</template
>
<icon-question-circle size="14" class="!color-#737478" />
</a-tooltip>
</a-space>
</template>
<template #rank="{ record }">
<img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" />
<span v-else>{{ record.rank }}</span>
</template>
<template #keywords="{ record }">
<a-tag
v-for="item in record.keywords"
:key="item"
class="!rounded-2px !px-8px !py-1px !bg-#F2F3F5 !h-22px !color-#3C4043 mb-5px mr-5px"
>{{ item }}</a-tag
>
</template>
<template #hot="{ record }">
<img
v-for="i in record.hot"
:key="i"
:src="starImages[i - 1]"
style="width: 16px; height: 16px"
class="mr-2px"
/>
</template>
<template #sentiment="{ record }">
<img v-if="record.felling == '2'" src="@/assets/img/hottranslation/good.png" class="w-24px h-24px" />
<img v-else-if="record.felling == '1'" src="@/assets/img/hottranslation/normal.png" class="w-24px h-24px" />
<img v-else-if="record.felling == '0'" src="@/assets/img/hottranslation/poor.png" class="w-24px h-24px" />
</template>
<template #optional="{ record }">
<a-button type="outline" class="!rounded-4px" @click="gotoDetail(record)">详情</a-button>
</template>
</a-table>
</a-space>
<a-modal :visible="visible" unmountOnClose modal-class="hot-translation-modal" width="640px" @cancel="handleCancel">
</Table>
</div>
<Modal
v-model:open="visible"
unmountOnClose
centered
wrapClassName="hot-translation-modal"
width="640px"
@cancel="handleCancel"
>
<template #title>
<span style="text-align: left; width: 100%">行业热门话题洞察</span>
</template>
<div>
<a-space direction="vertical">
<Space direction="vertical">
<div class="mb-4px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-48px">话题名称</p>
<span class="cts">{{ topicInfo.name }}</span>
@ -94,11 +105,11 @@
</div>
<div class="mb-4px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-48px">关键词</p>
<a-tag
<Tag
v-for="item in topicInfo.keywords"
:key="item"
class="mr-8px py-10px px-8px rounded-4px bg-#F2F3F5 cts !h-24px"
>{{ item }}</a-tag
>{{ item }}</Tag
>
</div>
<div class="mb-4px flex items-center">
@ -123,25 +134,27 @@
<p class="!mr-16px w-48px cts relative top-2px">原始来源</p>
<div class="flex flex-col">
<div v-for="item in topicInfo.industry_topic_sources" :key="item" class="mb-18px flex items-center">
<a-link style="background-color: initial" :href="item.link" target="_blank" class="!text-12px">{{
<Link :href="item.link" target="_blank" class="!text-12px">{{
item.title
}}</a-link>
}}</Link>
<img src="@/assets/img/hottranslation/xhs.png" width="16" height="16" />
</div>
</div>
</div>
</a-space>
</Space>
</div>
<template #footer>
<a-button size="large" @click="handleCancel">取消</a-button>
<a-button type="primary" size="large" class="rounded-4px" @click="handleOk"> 确定 </a-button>
<Button size="large" @click="handleCancel">取消</Button>
<Button type="primary" size="large" @click="handleOk"> 确定 </Button>
</template>
</a-modal>
</Modal>
</view>
</template>
<script setup>
import topHeader from './topHeader.vue';
import { Modal, Button, Tooltip, Space, Table, Tag, Typography } from 'ant-design-vue';
const { Link } = Typography;
import { ref, computed } from 'vue';
import { fetchIndustriesTree, fetchIndustryTopics, fetchIndustryTopicDetail } from '@/api/all/index';
import star1 from '@/assets/img/hottranslation/star-fill1.png';
@ -251,7 +264,7 @@ onMounted(() => {
const getIndustriesTree = async () => {
const res = await fetchIndustriesTree();
industriesTree.value = res;
selectedIndustry.value = res[0].id;
selectedIndustry.value = res[0]?.id;
getIndustryTopics();
};
@ -320,7 +333,7 @@ const handleOk = () => {
color: #6d4cfe !important;
border-color: #6d4cfe !important;
}
:deep(.arco-modal-body) {
:deep(.ant-modal-body) {
padding: 0px;
}
@ -374,7 +387,7 @@ const handleOk = () => {
}
}
.arco-modal-body {
.ant-modal-body {
padding: 12px 20px 0;
.cts {
color: var(--Text-2, #3c4043);

View File

@ -3,102 +3,122 @@
<view>
<topHeader ref="topHeaderRef" @click="search"></topHeader>
<!-- 重点品牌列表 -->
<a-space
<Space
direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px"
>
<div class="title-row">
<span class="title mr-4px">重点品牌列表</span>
<a-tooltip>
<template #content
<Tooltip>
<template #title
>基于该行业中近期提及频次高用户互动活跃的品牌内容筛选出关注度较高的代表性品牌</template
>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<a-table
:columns="columns"
:data="dataList"
:filter-icon-align-left="alignLeft"
:scroll="true"
<Table
:dataSource="dataList"
:pagination="false"
:showSorterTooltip="false"
@change="handleChange"
>
<template #empty>
<Table.Column
v-for="column in columns"
:key="column.dataIndex"
:title="column.title"
:dataIndex="column.dataIndex"
:width="column.width"
:minWidth="column.minWidth"
:sortable="column.sortable"
>
<template v-if="column.slotName === 'rank'" #customRender="{ record }">
<img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" />
<span v-else>{{ record.rank }}</span>
</template>
<template v-else-if="column.slotName === 'hot'" #customRender="{ record }">
<img v-for="i in record.hot" :key="i" :src="starImages[i - 1]" style="width: 16px; height: 16px" />
</template>
<template v-else-if="column.slotName === 'trend'" #customRender="{ record }">
<div class="flex items-center" :class="record.trend > 0 ? 'color-#F64B31' : 'color-#25C883'">
<icon-arrow-up v-if="record.trend > 0" size="16" />
<icon-arrow-down v-else size="16" />
{{ `${(record.trend * 100).toFixed(2)}%` }}
</div>
</template>
<template v-else-if="column.slotName === 'volumeRate'" #customRender="{ record }">
<Statistic :value="record.volume_rate * 100" />%
</template>
<template v-else-if="column.titleSlotName === 'hotTitle'" #title>
<Space>
<span>热度指数</span>
<Tooltip>
<template #title>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template>
<icon-question-circle size="14" class="!color-#737478" />
</Tooltip>
</Space>
</template>
<template v-else-if="column.titleSlotName === 'trendTitle'" #title>
<Space>
<span>变化幅度</span>
<Tooltip>
<template #title>仅基于品牌出现频次</template>
<icon-question-circle size="14" class="!color-#737478" />
</Tooltip>
</Space>
</template>
<template v-else-if="column.titleSlotName === 'volume_rateTitle'" #title>
<Space>
<span>占总声量比例</span>
<Tooltip>
<template #title>该品牌在当前周期内被提及的内容量占整个行业内容总量的比例</template>
<icon-question-circle size="14" class="!color-#737478" />
</Tooltip>
</Space>
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
<template #hotTitle>
<a-space>
<span>热度指数</span>
<a-tooltip>
<template #content>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template>
<icon-question-circle size="14" class="!color-#737478" />
</a-tooltip>
</a-space>
</template>
<template #trendTitle>
<a-space>
<span>变化幅度</span>
<a-tooltip>
<template #content>仅基于品牌出现频次</template>
<icon-question-circle size="14" class="!color-#737478" />
</a-tooltip>
</a-space>
</template>
<template #volume_rateTitle>
<a-space>
<span>占总声量比例</span>
<a-tooltip>
<template #content>该品牌在当前周期内被提及的内容量占整个行业内容总量的比例</template>
<icon-question-circle size="14" class="!color-#737478" />
</a-tooltip>
</a-space>
</template>
<template #rank="{ record }">
<img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" />
<span v-else>{{ record.rank }}</span>
</template>
<template #hot="{ record }">
<img v-for="i in record.hot" :key="i" :src="starImages[i - 1]" style="width: 16px; height: 16px" />
</template>
<template #trend="{ record }">
<div class="flex items-center" :class="record.trend > 0 ? 'color-#F64B31' : 'color-#25C883'">
<icon-arrow-up v-if="record.trend > 0" size="16" />
<icon-arrow-down v-else size="16" />
{{ `${(record.trend * 100).toFixed(2)}%` }}
</div>
</template>
<template #volumeRate="{ record }"> <a-statistic :value="record.volume_rate * 100" />% </template>
</a-table>
</a-space>
</Table>
</Space>
<!-- 舆情 & 敏感动态-->
<a-space
<Space
direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px"
>
<div class="title-row">
<span class="title mr-4px">舆情 & 敏感动态</span>
<a-tooltip>
<template #content
<Tooltip>
<template #title
>基于情绪分析与敏感词识别对行业内容中的负面或争议性话题进行监测辅助判断舆情风险动态</template
>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<a-table :data="otherList" :columns="columns2" :pagination="false" :scroll="true" style="font-size: 12px">
<template #empty>
<Table :dataSource="otherList" :pagination="false" :showSorterTooltip="false" style="font-size: 12px">
<Table.Column
v-for="column in columns2"
:key="column.dataIndex"
:title="column.title"
:dataIndex="column.dataIndex"
:width="column.width"
:minWidth="column.minWidth"
:sortable="column.sortable"
/>
<template #emptyText>
<NoData />
</template>
</a-table>
</a-space>
</Table>
</Space>
</view>
</template>
<script setup>
import topHeader from './topHeader.vue';
import { Tooltip, Space, Table, Statistic } from 'ant-design-vue';
import { fetchFocusBrandsList, fetchEventDynamicsList } from '@/api/all/index';
import { ref, onMounted, computed } from 'vue';
import star1 from '@/assets/img/hottranslation/star-fill1.png';

View File

@ -3,46 +3,46 @@
<view>
<topHeader ref="topHeaderRef" @search="search"></topHeader>
<!-- 关键词热度榜 -->
<a-space
<Space
direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px mb-20px"
>
<div class="title-row">
<span class="title mr-4px">关键词热度榜</span>
<a-tooltip>
<template #content>基于该行业用户内容中提及频率较高的关键词按热度进行排序反映近期关注焦点</template>
<Tooltip>
<template #title>基于该行业用户内容中提及频率较高的关键词按热度进行排序反映近期关注焦点</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<a-table
<Table
:columns="columns"
:data="dataList"
:filter-icon-align-left="alignLeft"
:scroll="true"
:dataSource="dataList"
:scroll="{ x: true }"
:pagination="false"
:showSorterTooltip="false"
@change="handleChange"
>
<template #empty>
<template #emptyText>
<NoData />
</template>
<template #heatLevel>
<a-space>
<Space>
<span>热度指数</span>
<a-tooltip>
<template #content>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template>
<Tooltip>
<template #title>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template>
<icon-question-circle size="14" class="!color-#737478" />
</a-tooltip>
</a-space>
</Tooltip>
</Space>
</template>
<template #trendTitle>
<a-space>
<Space>
<span>变化幅度</span>
<a-tooltip>
<template #content>仅基于关键词出现频次</template>
<Tooltip>
<template #title>仅基于关键词出现频次</template>
<icon-question-circle size="14" class="!color-#737478" />
</a-tooltip>
</a-space>
</Tooltip>
</Space>
</template>
<template #rank="{ record }">
@ -52,7 +52,7 @@
<span v-else>{{ record.rank }}</span>
</template>
<template #keywords="{ record }">
<a-tag v-for="item in record.keywords" :key="item" style="margin-right: 5px">{{ item }}</a-tag>
<Tag v-for="item in record.keywords" :key="item" style="margin-right: 5px">{{ item }}</Tag>
</template>
<template #hot="{ record }">
<img v-for="i in record.hot" :key="i" :src="starImages[i - 1]" style="width: 16px; height: 16px" />
@ -69,21 +69,21 @@
{{ `${(record.trend * 100).toFixed(2)}%` }}
</div>
</template>
</a-table>
</a-space>
</Table>
</Space>
<!-- 行业情绪 -->
<a-space
<Space
direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px"
>
<div class="title-row">
<span class="title mr-4px">行业情绪</span>
<a-tooltip>
<template #content
<Tooltip>
<template #title
>对该行业下用户内容进行情绪分析按情绪类别统计占比提取占比最高者作为行业情绪代表</template
>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<div class="flex items-center w-100%">
@ -103,17 +103,16 @@
</div>
</div>
</div>
<a-table
<Table
class="flex-1"
:columns="columns2"
:data="sortedRowData"
:span-method="spanMethod"
:filter-icon-align-left="alignLeft"
:scroll="true"
:dataSource="sortedRowData"
:scroll="{ x: true }"
:pagination="false"
:showSorterTooltip="false"
@change="handleChange"
>
<template #empty>
<template #emptyText>
<NoData />
</template>
<template #felling="{ record }">
@ -122,33 +121,33 @@
<span>{{ fellingStatus[record.felling].label }}</span>
</div>
</template>
</a-table>
</Table>
</div>
</a-space>
</Space>
<!-- 新兴关键词 -->
<a-space
<Space
direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px"
>
<div class="title-row">
<span class="title mr-4px">新兴关键词</span>
<a-tooltip>
<template #content
<Tooltip>
<template #title
>指当前周期中首次出现或相较上一周期词频显著增长的关键词反映近期出现的新关注点</template
>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<a-table
<Table
:columns="columns3"
:data="keywordList"
:filter-icon-align-left="alignLeft"
:scroll="true"
:dataSource="keywordList"
:scroll="{ x: true }"
:pagination="false"
:showSorterTooltip="false"
@change="handleChange"
>
<template #empty>
<template #emptyText>
<NoData />
</template>
<template #rank="{ record }">
@ -181,23 +180,23 @@
<img v-for="i in record.hot" :key="i" :src="starImages[i - 1]" style="width: 16px; height: 16px" />
</template>
<template #hotTitle="{ record }">
<a-space>
<template #hotTitle>
<Space>
<span>当前热度指数</span>
<a-tooltip>
<template #content>综合关键词出现频次互动表现如点赞收藏评论加权计算的热度得分</template>
<Tooltip>
<template #title>综合关键词出现频次互动表现如点赞收藏评论加权计算的热度得分</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</a-space>
</Tooltip>
</Space>
</template>
<template #trendTitle="{ record }">
<a-space>
<template #trendTitle>
<Space>
<span>变化幅度</span>
<a-tooltip>
<template #content>仅基于关键词出现频次</template>
<Tooltip>
<template #title>仅基于关键词出现频次</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</a-space>
</Tooltip>
</Space>
</template>
<template #tred="{ record }">
<div class="flex items-center" :class="record.trend > 0 ? 'color-#F64B31' : 'color-#25C883'">
@ -207,15 +206,16 @@
</div>
</template>
<template #optional="{ record }">
<a-button type="outline" @click="gotoDetail(record)">详情</a-button>
<Button type="primary" ghost @click="gotoDetail(record)">详情</Button>
</template>
</a-table>
</a-space>
</Table>
</Space>
<!-- modal -->
<a-modal
:visible="visible"
modal-class="keyword-modal"
<Modal
v-model:open="visible"
wrapClassName="keyword-modal"
unmountOnClose
centered
width="640px"
@ok="handleOk"
@cancel="handleCancel"
@ -224,7 +224,7 @@
<span style="text-align: left; width: 100%">新兴关键词</span>
</template>
<div>
<a-space direction="vertical">
<Space direction="vertical">
<div class="mb-12px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-83px">话题名称</p>
<span class="cts">{{ topicInfo.name }}</span>
@ -250,25 +250,27 @@
<p class="!mr-16px w-83px cts relative top-2px">原始来源</p>
<div class="flex flex-col">
<div v-for="item in topicInfo.industry_new_keyword_sources" :key="item" class="mb-18px flex items-center">
<a-link style="background-color: initial" :href="item.link" target="_blank" class="!text-12px">{{
<Link :href="item.link" target="_blank" class="!text-12px">{{
item.title
}}</a-link>
}}</Link>
<img src="@/assets/img/hottranslation/xhs.png" width="16" height="16" />
</div>
</div>
</div>
</a-space>
</Space>
</div>
<template #footer>
<a-button size="large" @click="handleCancel">取消</a-button>
<a-button type="primary" size="large" class="rounded-4px" @click="handleOk"> 确定 </a-button>
<Button size="large" @click="handleCancel">取消</Button>
<Button type="primary" size="large" @click="handleOk"> 确定 </Button>
</template>
</a-modal>
</Modal>
</view>
</template>
<script setup>
import topHeader from './topHeader.vue';
import { Modal, Button, Tooltip, Space, Table, Tag, Typography } from 'ant-design-vue';
const { Link } = Typography;
import {
fetchKeywordTrendsList,
fetchIndustryEmotions,
@ -690,7 +692,7 @@ onMounted(() => {
}
}
.arco-modal-body {
.ant-modal-body {
padding: 12px 20px 0;
.cts {
color: var(--Text-2, #3c4043);

View File

@ -1,97 +1,100 @@
<template>
<view>
<!-- 头部 -->
<a-space
<Space
direction="vertical"
class="bg-#fff rounded-8px mb-20px"
style="background-color: #fff; width: 100%; padding: 24px; color: #737478; font-size: 14px"
>
<a-space align="start" style="width: 100%; align-items: flex-start" class="mb-12px">
<Space align="start" style="width: 100%; align-items: flex-start" class="mb-12px">
<span style="flex-shrink: 0; line-height: 28px" class="mr-32px">行业大类</span>
<div style="display: flex; flex-wrap: wrap; gap: 16px; width: 100%; align-items: flex-start">
<a-tag
<Tag
v-for="item in industriesTree"
:key="item.id"
size="Medium"
:checkable="true"
:checked="selectedIndustry == item.id"
style="padding: 10px 16px; border-radius: 30px; height: 28px"
style="padding: 0 16px; border-radius: 30px; height: 28px"
class="lh-28px cursor-pointer"
:style="
selectedIndustry == item.id
? 'color: #6D4CFE; background-color: #F0EDFF'
: 'color: #3C4043; background-color: #F7F8FA'
"
@check="handleIndustryCheck(item.id)"
>{{ item.name }}</a-tag
@click="handleIndustryCheck(item.id)"
>{{ item.name }}</Tag
>
</div>
</a-space>
</Space>
<!-- 二级类目 -->
<a-space align="start" style="width: 100%; align-items: flex-start" class="mb-12px">
<Space align="start" style="width: 100%; align-items: flex-start" class="mb-12px">
<span style="flex-shrink: 0; line-height: 28px" class="mr-32px">二级类目</span>
<div style="display: flex; flex-wrap: wrap; gap: 16px; width: 100%; align-items: flex-start">
<a-tag
<Tag
v-for="item in subCategories"
:key="item.id"
size="Medium"
size="small"
:checkable="true"
:checked="selectedSubCategory == item.id"
style="padding: 10px 16px; border-radius: 30px; height: 28px"
style="padding: 0 16px; border-radius: 30px; height: 28px"
class="lh-28px cursor-pointer"
:style="
selectedSubCategory == item.id
? 'color: #6d4cfe; background-color: #f0edff'
: 'color: #3C4043; background-color: #F7F8FA'
"
@check="handleSubCategoryCheck(item.id)"
>{{ item.name }}</a-tag
@click="handleSubCategoryCheck(item.id)"
>{{ item.name }}</Tag
>
</div>
</a-space>
<!-- </a-space> -->
<a-space align="start" style="width: 100%; align-items: flex-start" class="mb-12px">
</Space>
<!-- </Space> -->
<Space align="start" style="width: 100%; align-items: flex-start" class="mb-12px">
<span style="flex-shrink: 0; line-height: 28px" class="mr-32px">时间筛选</span>
<div style="display: flex; flex-wrap: wrap; gap: 16px; width: 100%; align-items: flex-start">
<a-tag
<Tag
v-for="item in timePeriods"
:key="item.value"
size="Medium"
:checkable="true"
:checked="selectedTimePeriod == item.value"
style="padding: 10px 16px; border-radius: 30px; height: 28px"
class="lh-28px cursor-pointer"
style="padding: 0 16px; border-radius: 30px; height: 28px"
:style="
selectedTimePeriod == item.value
? 'color: #6d4cfe; background-color: #f0edff'
: 'color: #3C4043; background-color: #F7F8FA'
"
@check="handleTimePeriodCheck(item.value)"
@click="handleTimePeriodCheck(item.value)"
>{{ item.label }}
</a-tag>
</Tag>
</div>
</a-space>
</Space>
<!-- 搜索区域 -->
<a-space style="margin-left: 'auto'">
<a-button type="primary" size="medium" @click="handleSearch">
<Space style="margin-left: 'auto'">
<Button type="primary" @click="handleSearch">
<template #icon>
<icon-search />
<icon-search class="mr-8px"/>
</template>
<!-- Use the default slot to avoid extra spaces -->
<template #default>搜索</template>
</a-button>
<a-button class="w-84px reset-btn" size="medium" @click="handleReset">
</Button>
<Button class="w-84px reset-btn" @click="handleReset">
<template #icon>
<icon-refresh />
<icon-refresh class="mr-8px"/>
</template>
<template #default>重置</template>
</a-button>
</a-space>
</a-space>
</Button>
</Space>
</Space>
</view>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { fetchIndustriesTree } from '@/api/all/index';
import { Button, Space, Tag } from 'ant-design-vue';
const emit = defineEmits<(e: 'search') => void>();
// 行业大类
const industriesTree = ref([]);
@ -139,6 +142,7 @@ const handleIndustryCheck = (id) => {
};
const handleSubCategoryCheck = (id) => {
console.log('handleSubCategoryCheck');
selectedSubCategory.value = id;
};
@ -185,7 +189,7 @@ const handleReset = () => {
color: #6d4cfe !important;
border-color: #6d4cfe !important;
}
:deep(.arco-modal-body) {
:deep(.ant-modal-body) {
padding: 0px;
}
.reset-btn {

View File

@ -3,63 +3,71 @@
<topHeader ref="topHeaderRef" @search="search"></topHeader>
<!-- 用户痛点观察 -->
<a-space
<Space
direction="vertical"
style="background-color: #fff; width: 100%; padding: 0 20px"
class="bg-#fff rounded-8px mb-24px"
>
<div class="title-row">
<span class="title mr-4px">用户痛点观察</span>
<a-tooltip>
<template #content
<Tooltip>
<template #title
>基于用户内容中的情绪分析与表达模式提取反复出现的负面倾向主题反映典型使用痛点</template
>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<a-table
:columns="columns"
:data="dataList"
:filter-icon-align-left="alignLeft"
:scroll="true"
<Table
:dataSource="dataList"
:pagination="false"
:showSorterTooltip="false"
@change="handleChange"
>
<template #empty>
<Table.Column
v-for="column in columns"
:key="column.dataIndex"
:title="column.title"
:dataIndex="column.dataIndex"
:width="column.width"
:minWidth="column.minWidth"
:sortable="column.sortable"
>
<template v-if="column.slotName === 'rank'" #customRender="{ record }">
<img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" />
<span v-else>{{ record.rank }}</span>
</template>
<template v-else-if="column.slotName === 'keywords'" #customRender="{ record }">
<Tag
v-for="item in record.keywords"
:key="item"
class="!rounded-2px !px-8px !py-1px !bg-#F2F3F5 !h-22px !color-#3C4043 mb-5px mr-5px"
>{{ item }}</Tag
>
</template>
<template v-else-if="column.slotName === 'frequency'" #customRender="{ record }">
<Tag
:class="`!rounded-2px !px-8px !py-1px !bg-${frequencyStatus[record.frequency].bgColor} !h-22px !color-${
frequencyStatus[record.frequency].color
}`"
>{{ frequencyStatus[record.frequency].label }}</Tag
>
</template>
<template v-else-if="column.slotName === 'optional'" #customRender="{ record }">
<Button type="primary" ghost @click="gotoDetail(record)">详情</Button>
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
<template #rank="{ record }">
<img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" />
<span v-else>{{ record.rank }}</span>
</template>
<template #keywords="{ record }">
<a-tag
v-for="item in record.keywords"
:key="item"
class="!rounded-2px !px-8px !py-1px !bg-#F2F3F5 !h-22px !color-#3C4043 mb-5px mr-5px"
>{{ item }}</a-tag
>
</template>
<template #frequency="{ record }">
<a-tag
:class="`!rounded-2px !px-8px !py-1px !bg-${frequencyStatus[record.frequency].bgColor} !h-22px !color-${
frequencyStatus[record.frequency].color
}`"
>{{ frequencyStatus[record.frequency].label }}</a-tag
>
</template>
</Table>
</Space>
<template #optional="{ record }">
<a-button type="outline" class="!rounded-4px" @click="gotoDetail(record)">详情</a-button>
</template>
</a-table>
</a-space>
<a-modal
:visible="visible"
modal-class="user-pain-points-modal"
<Modal
v-model:open="visible"
wrapClassName="user-pain-points-modal"
centered
unmountOnClose
width="640px"
@ok="handleOk"
@ -69,27 +77,27 @@
<span style="text-align: left; width: 100%">用户痛点观察</span>
</template>
<div>
<a-space direction="vertical" style="font-size: 12px">
<Space direction="vertical" style="font-size: 12px">
<div class="mb-12px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-60px">痛点</p>
<span class="cts">{{ topicInfo.name }}</span>
</div>
<div class="mb-12px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-60px">关键词</p>
<a-tag
<Tag
v-for="item in topicInfo.keywords"
:key="item"
class="mr-8px py-10px px-8px rounded-4px bg-#F2F3F5 cts !h-24px"
>{{ item }}</a-tag
>{{ item }}</Tag
>
</div>
<div class="mb-12px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-60px">频次</p>
<a-tag
<Tag
:class="`!rounded-2px !px-8px !py-1px !bg-${
frequencyStatus[topicInfo.frequency].bgColor
} !h-22px !color-${frequencyStatus[topicInfo.frequency].color}`"
>{{ frequencyStatus[topicInfo.frequency].label }}</a-tag
>{{ frequencyStatus[topicInfo.frequency].label }}</Tag
>
</div>
<div class="mb-12px flex items-center">
@ -100,25 +108,27 @@
<p class="cts !mr-16px flex-shrink-0 w-60px">原始来源</p>
<div class="flex flex-col">
<div v-for="item in topicInfo.user_pain_point_sources" :key="item" class="mb-18px flex items-center">
<a-link style="background-color: initial" :href="item.link" target="_blank" class="!text-12px">{{
<Link :href="item.link" target="_blank" class="!text-12px">{{
item.title
}}</a-link>
}}</Link>
<img src="@/assets/img/hottranslation/xhs.png" width="16" height="16" />
</div>
</div>
</div>
</a-space>
</Space>
</div>
<template #footer>
<a-button size="large" @click="handleCancel">取消</a-button>
<a-button type="primary" size="large" class="rounded-4px" @click="handleOk"> 确定 </a-button>
<Button size="large" @click="handleCancel">取消</Button>
<Button type="primary" size="large" @click="handleOk"> 确定 </Button>
</template>
</a-modal>
</Modal>
</view>
</template>
<script setup>
import topHeader from './topHeader.vue';
import { Modal, Button, Tooltip, Space, Table, Tag, Typography } from 'ant-design-vue';
const { Link } = Typography;
import { fetchUserPainPointsDetail, fetchUserPainPointsList } from '@/api/all/index';
import { ref, onMounted, computed } from 'vue';
import top1 from '@/assets/img/captcha/top1.svg';
@ -286,16 +296,16 @@ const search = () => {
</style>
<style lang="scss">
.user-pain-points-modal {
.arco-modal-header {
.ant-modal-header {
border-bottom: none;
height: 56px;
padding: 0 20px;
.arco-modal-title {
.ant-modal-title {
justify-content: flex-start;
}
}
.arco-modal-body {
.ant-modal-body {
padding: 12px 20px 0;
.cts {
color: var(--Text-2, #3c4043);
@ -311,7 +321,7 @@ const search = () => {
}
}
.arco-modal-footer {
.ant-modal-footer {
display: flex;
height: 64px;
padding: 0px 20px;

View File

@ -6,52 +6,52 @@
<div class="bg-#fff rounded-8px w-100% py-0 px-20px w-600px mr-24px">
<div class="title-row">
<span class="title mr-4px">性别分布</span>
<a-tooltip>
<template #content>基于社交内容平台中用户资料互动行为及语义特征进行智能识别与估算</template>
<Tooltip>
<template #title>基于社交内容平台中用户资料互动行为及语义特征进行智能识别与估算</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<a-space v-if="genderData.length > 0">
<Space v-if="genderData.length > 0">
<div id="container" class="w-300px h-300px"></div>
<a-space direction="vertical" style="font-size: 14px">
<a-space>
<Space direction="vertical" style="font-size: 14px">
<Space>
<span style="width: 8px; height: 8px; background-color: #f64b31; border-radius: 50%"></span>
<span>女性</span>
<span>{{ (girlData.rate * 100).toFixed(2) }}%</span>
<span>TGI</span>
<span>{{ girlData.tgi }}</span>
</a-space>
<a-space>
</Space>
<Space>
<span style="width: 8px; height: 8px; background-color: #2a59f3; border-radius: 50%"></span>
<span>男性</span>
<span>{{ (boyData.rate * 100).toFixed(2) }}%</span>
<span>TGI</span>
<span>{{ boyData.tgi }}</span>
</a-space>
</a-space>
</a-space>
</Space>
</Space>
</Space>
<div v-else>
<NoData class="w-100% h-100%" />
</div>
</div>
<!-- 2. 年龄分布 -->
<div class="bg-#fff rounded-8px w-100% py-0 px-20px flex-1 flex flex-col">
<a-space style="display: flex; justify-content: space-between; width: 100%; font-size: 12px">
<Space style="display: flex; justify-content: space-between; width: 100%; font-size: 12px">
<div class="title-row">
<span class="title mr-4px">年龄分布</span>
<a-tooltip>
<template #content>基于社交平台的公开信息内容偏好与行为模式通过算法进行年龄段归类和统计</template>
<Tooltip>
<template #title>基于社交平台的公开信息内容偏好与行为模式通过算法进行年龄段归类和统计</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<a-space v-if="ageValueData.length > 0" align="center">
<Space v-if="ageValueData.length > 0" align="center">
<span style="width: 16px; height: 8px; background-color: #6d4cfe; border-radius: 2px"></span>
<span style="color: #6d4cfe">占比</span>
<span style="width: 16px; height: 8px; background-color: #f64b31; border-radius: 2px"></span>
<span style="color: #f64b31">TGI比</span>
</a-space>
</a-space>
</Space>
</Space>
<div v-if="ageValueData.length === 0" class="w-100% flex-1">
<NoData />
</div>
@ -61,17 +61,17 @@
<div class="bg-#fff rounded-8px w-100% py-0 px-20px flex-1 pb-20px">
<div class="title-row">
<span class="title mr-4px">地域分布</span>
<a-tooltip>
<template #content>基于社交平台的IP归属地位置标签内容发布地等数据推测用户活跃区域</template>
<Tooltip>
<template #title>基于社交平台的IP归属地位置标签内容发布地等数据推测用户活跃区域</template>
<icon-question-circle size="16" class="!color-#737478" />
</a-tooltip>
</Tooltip>
</div>
<div class="flex">
<a-space direction="vertical">
<Space direction="vertical">
<div id="chinaMap" style="height: 416px; width: 640px"></div>
<a-space direction="vertical" style="font-size: 14px">
<Space direction="vertical" style="font-size: 14px">
<span class="cts">搜索指数</span>
<a-space>
<Space>
<span class="cts"></span>
<span
v-for="item in 5"
@ -86,44 +86,38 @@
}"
></span>
<span class="cts"></span>
</a-space>
</a-space>
</a-space>
</Space>
</Space>
</Space>
<div class="flex flex-col h-486px">
<a-tabs default-active-key="1" class="h-100%" @change="tabChange">
<a-tab-pane key="1" title="省份">
<a-table :data="geoList" :pagination="false" class="h-100%" :scroll="{ y: '100%' }">
<template #empty>
<Tabs defaultActiveKey="1" class="h-100%" @change="tabChange" size="large">
<TabPane key="1" tab="省份">
<Table :dataSource="geoList" :pagination="false" class="h-100%" :scroll="{ y: '100%' }" :showSorterTooltip="false">
<template #emptyText>
<NoData />
</template>
<template #columns>
<a-table-column title="排名" data-index="rank" />
<a-table-column title="省份" data-index="geo" />
<a-table-column title="分布占比" data-index="rate" />
<a-table-column title="TGI指数" data-index="tgi" />
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="2" title="城市">
<a-table :data="geoList" :pagination="false" class="h-100%" :scroll="{ y: '100%' }">
<template #empty>
<Column title="排名" dataIndex="rank" />
<Column title="省份" dataIndex="geo" />
<Column title="分布占比" dataIndex="rate" />
<Column title="TGI指数" dataIndex="tgi" />
</Table>
</TabPane>
<TabPane key="2" tab="城市">
<Table :dataSource="geoList" :pagination="false" class="h-100%" :scroll="{ y: '100%' }" :showSorterTooltip="false">
<template #emptyText>
<NoData />
</template>
<template #columns>
<a-table-column title="排名" data-index="rank" />
<a-table-column title="城市" data-index="geo" />
<a-table-column title="分布占比" data-index="rate">
<template #cell="{ record }">
<span class="cts">{{ (record.rate * 100).toFixed(2) }}%</span>
</template>
</a-table-column>
<a-table-column title="TGI指数" data-index="tgi" />
</template>
</a-table>
</a-tab-pane>
</a-tabs>
<Column title="排名" dataIndex="rank" />
<Column title="城市" dataIndex="geo" />
<Column title="分布占比" dataIndex="rate">
<template #customRender="{ record }">
<span class="cts">{{ (record.rate * 100).toFixed(2) }}%</span>
</template>
</Column>
<Column title="TGI指数" dataIndex="tgi" />
</Table>
</TabPane>
</Tabs>
</div>
</div>
</div>
@ -133,11 +127,15 @@
<script setup>
import topHeader from './topHeader.vue';
import { fetchAgeDistributionsList, fetchGeoDistributionsList, fetchGenderDistributionsList } from '@/api/all/index';
import { ref, onMounted, computed } from 'vue';
import { ref, onMounted, computed, watch, nextTick } from 'vue';
import * as echarts from 'echarts';
import chinaJson from '@/assets/maps/china.json';
echarts.registerMap('china', chinaJson);
import { Tabs, Tooltip, Space, Table } from 'ant-design-vue';
const { TabPane } = Tabs;
const { Column } = Table;
const scope = ref(1); // 地域范围1-省2-市
const chartInstance = (ref < echarts.ECharts) | (null > null);
const topHeaderRef = ref();

View File

@ -2,40 +2,44 @@
<div class="bg-#fff rounded-8px w-100% py-0 px-20px pb-24px">
<div class="title-row">
<span class="title">账号管理</span>
<a-button type="outline" class="add-account-button" @click="handleAddAccount">添加子账号</a-button>
<Button type="primary" ghost class="add-account-button" @click="handleAddAccount">添加子账号</Button>
</div>
<a-table
:columns="columns"
:data="dataSource"
<Table
:dataSource="dataSource"
:pagination="pagination"
:showSorterTooltip="false"
class="mt-8px"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
@change="handleTableChange"
>
<template #empty>
<Table.Column title="手机号" dataIndex="mobile">
<template #customRender="{ record }">
<div class="flex item-center pt-13px pb-13px">
<span class="mr-4px">{{ record.mobile }}</span>
<Tag v-if="record.type === 0" class="primary-account">主账号</Tag>
<Tag v-else class="sub-account">子账号</Tag>
</div>
</template>
</Table.Column>
<Table.Column title="操作" dataIndex="action" width="120">
<template #customRender="{ record }">
<Button
v-if="record.type !== 0"
class="delete-button"
size="small"
type="primary"
ghost
danger
@click="openDeleteModal(record)"
>
删除
</Button>
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
<template #mobile="{ record }">
<div class="flex item-center pt-13px pb-13px">
<span class="mr-4px">{{ record.mobile }}</span>
<a-tag v-if="record.type === 0" class="primary-account">主账号</a-tag>
<a-tag v-else class="sub-account">子账号</a-tag>
</div>
</template>
<template #action="{ record }">
<a-button
v-if="record.type !== 0"
class="delete-button"
size="mini"
type="outline"
status="danger"
@click="openDeleteModal(record)"
>
删除
</a-button>
</template>
</a-table>
<Modal v-model:visible="addAccountVisible" width="480px" title="添加子账号" :okText="okText" @ok="handleOk">
</Table>
<Modal v-model:open="addAccountVisible" centered width="480px" title="添加子账号" :okText="okText" @ok="handleOk" >
<div v-if="canAddAccount" class="add-account-container">
<h2 class="add-account-title">生成企业专属链接成员通过访问即可注册并加入企业账号</h2>
<p class="add-account-subtitle">子账号可独立登录权限继承主账号配置</p>
@ -52,15 +56,15 @@
<p class="cannot-add-account-subtitle">如需添加更多子账号您可联系销售人员进行购买和权限扩展</p>
</div>
</Modal>
<CustomerServiceModal v-model:visible="customerServiceVisible" />
<DeleteModal v-model:visible="deleteVisible" :title="deleteTitle" @ok="handleDelete">
<p class="delete-modal-content">删除后该账号将无法登录您的企业</p>
<CustomerServiceModal v-model:open="customerServiceVisible" centered/>
<DeleteModal v-model:open="deleteVisible" centered :content="deleteTitle" @ok="handleDelete" @cancel="deleteVisible = false">
</DeleteModal>
</div>
</template>
<script setup lang="ts">
import Container from '@/components/container.vue';
import { ref, onMounted, reactive, computed } from 'vue';
import { Button, Table, message, Tag } from 'ant-design-vue';
import { fetchSubAccountPage, removeEnterpriseAccount, getEnterpriseInviteCode } from '@/api/all';
import Modal from '@/components/modal.vue';
import DeleteModal from '@/components/delete-modal.vue';
@ -82,10 +86,10 @@ const columns = [
const dataSource = ref([]);
const pagination = reactive({
total: 0,
showPageSize: true,
showTotal: true,
defaultCurrent: 1,
defaultPageSize: 10,
showSizeChanger: true,
showTotal: (total: number, range: [number, number]) => `${total} 条记录`,
current: 1,
pageSize: 10,
});
const params = reactive({
@ -120,13 +124,12 @@ const currentSelectAccount = ref();
const { copy, copied, isSupported } = useClipboard({ source: inviteUrl });
function handlePageChange(current: number) {
params.page = current;
getSubAccount();
}
function handlePageSizeChange(pageSize: number) {
params.page_size = pageSize;
function handleTableChange(paginationInfo: any, filters: any, sorter: any) {
params.page = paginationInfo.current;
params.page_size = paginationInfo.pageSize;
// 更新分页状态
pagination.current = paginationInfo.current;
pagination.pageSize = paginationInfo.pageSize;
getSubAccount();
}
@ -155,13 +158,13 @@ function handleOk() {
return;
}
if (!isSupported) {
AMessage.error('您的浏览器不支持复制,请手动复制!');
message.error('您的浏览器不支持复制,请手动复制!');
}
copy(inviteUrl.value);
if (!copied) {
AMessage.error('复制失败,请手动复制!');
message.error('复制失败,请手动复制!');
}
AMessage.success('复制成功!');
message.success('复制成功!');
}
function openDeleteModal(record: { id: number; mobile: string }) {
@ -172,7 +175,7 @@ function openDeleteModal(record: { id: number; mobile: string }) {
async function handleDelete() {
await removeEnterpriseAccount(currentSelectAccount.value.id);
AMessage.success('移除成功!');
message.success('移除成功!');
}
onMounted(() => {
@ -275,14 +278,7 @@ onMounted(() => {
color: var(--Text-2, rgba(60, 64, 67, 1));
}
}
.delete-modal-content {
margin-left: 34px;
margin-top: 16px;
font-family: $font-family-medium;
font-weight: 400;
font-size: 12px;
color: var(--Text-2, rgba(60, 64, 67, 1));
}
.title-row {
display: flex;
height: 64px;

View File

@ -3,39 +3,53 @@
<div class="title-row">
<span class="title">企业信息</span>
</div>
<a-table :columns="columns" :data="dataSource" :pagination="false" class="mt-8px">
<template #empty>
<Table :dataSource="dataSource" :pagination="false" :showSorterTooltip="false" class="mt-8px">
<Table.Column title="企业信息" dataIndex="info">
<template #customRender="{ record }">
{{ record.name }}
</template>
</Table.Column>
<Table.Column title="操作" dataIndex="action" width="120">
<template #customRender>
<Button class="edit-button" size="small" type="primary" ghost @click="handleUpdate">修改</Button>
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
<template #info="{ record }">
{{ record.name }}
</template>
<template #action>
<a-button class="edit-button" size="mini" type="outline" @click="handleUpdate">修改</a-button>
</template>
</a-table>
<Modal v-model:visible="infoVisible" width="480px" title="修改企业名称" :okText="okText" @ok="handleOk">
</Table>
<Modal
v-model:open="infoVisible"
width="480px"
centered
title="修改企业名称"
:okText="okText"
@ok="handleOk"
cancelText="取消"
>
<p class="tips">
企业名称只能修改2次请谨慎操作<span
>剩余{{ enterpriseInfo!.update_name_quota - enterpriseInfo!.used_update_name_count }}
</span>
</p>
<a-form
<Form
:model="form"
:rules="rules"
class="form"
:label-col-props="{ span: 6, offset: 0 }"
:wrapper-col-props="{ span: 18, offset: 0 }"
label-align="left"
>
<a-form-item required field="name" label="新企业名称">
<a-input v-model.trim="form.name" size="small" :disabled="!canUpdate" placeholder="请输入新企业名称" />
</a-form-item>
</a-form>
<FormItem required name="name" label="新企业名称">
<Input v-model:value.trim="form.name" size="small" :disabled="!canUpdate" placeholder="请输入新企业名称" />
</FormItem>
</Form>
</Modal>
<CustomerServiceModal v-model:visible="customerServiceVisible" />
<CustomerServiceModal v-model:open="customerServiceVisible" centered />
</div>
</template>
<script setup lang="ts">
import { Button, Form, FormItem, Input, Table, message } from 'ant-design-vue';
import Container from '@/components/container.vue';
import Modal from '@/components/modal.vue';
import { ref, reactive, computed } from 'vue';
@ -48,6 +62,16 @@ const form = reactive({
name: '',
});
const rules = {
name: [
{
required: true,
message: '请输入新企业名称',
trigger: ['blur'],
},
],
};
const enterpriseInfo = computed(() => {
return store.enterpriseInfo ?? {};
});
@ -98,7 +122,7 @@ async function handleOk() {
await updateEnterpriseName({ name: form.name });
store.setEnterpriseName(form.name);
store.incUsedUpdateNameCount();
AMessage.success('修改成功!');
message.success('修改成功!');
}
</script>

View File

@ -1,32 +1,37 @@
<template>
<div class="bg-#fff rounded-16px w-100% p-36px">
<p class="title mb-32px">个人信息</p>
<a-table :columns="columns" :data="dataSource" :pagination="false" class="mt-8px">
<template #empty>
<Table :dataSource="dataSource" :pagination="false" :showSorterTooltip="false" class="mt-8px">
<Table.Column title="用户信息" dataIndex="info">
<template #customRender="{ record }">
<div class="pt-3px pb-3px">
<Avatar :src="record.head_image" :size="32" />
{{ record.name || '-' }}
<icon-edit size="13" class="ml-8px" @click="openEditInfoModal" />
</div>
</template>
</Table.Column>
<Table.Column title="手机号" dataIndex="mobile">
<template #customRender="{ record }">
{{ record.mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') }}
<icon-edit size="13" class="ml-8px" @click="openEditMobileModal" />
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
<template #info="{ record }">
<div class="pt-3px pb-3px">
<a-avatar :image-url="record.head_image" :size="32" />
{{ record.name || '-' }}
<icon-edit size="13" class="ml-8px" @click="openEditInfoModal" />
</div>
</template>
<template #mobile="{ record }">
{{ record.mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') }}
<icon-edit size="13" class="ml-8px" @click="openEditMobileModal" />
</template>
</a-table>
<Modal v-model:visible="infoVisible" title="修改用户信息" @ok="handleSubmitUserInfo">
<a-form
</Table>
<Modal v-model:open="infoVisible" centered title="修改用户信息" @ok="handleSubmitUserInfo">
<Form
class="form"
:rules="rules"
:model="userInfoForm"
:label-col-props="{ span: 3, offset: 0 }"
:wrapper-col-props="{ span: 21, offset: 0 }"
>
<a-form-item field="head_image" label="头像">
<FormItem name="head_image" label="头像">
<div class="flex items-center">
<a-avatar :image-url="userInfoForm.file_url" :size="48" />
<Avatar :src="userInfoForm.file_url" :size="48" />
<span class="upload-button" @click="triggerFileInput">
<input
ref="uploadInputRef"
@ -35,40 +40,40 @@
style="display: none"
@change="handleFileChange"
/>
<a-button><icon-upload />上传新头像</a-button>
<Button><icon-upload />上传新头像</Button>
</span>
</div>
</a-form-item>
<a-form-item field="name" label="昵称">
<a-input v-model.trim="userInfoForm.name" placeholder="请输入昵称" />
</a-form-item>
</a-form>
</FormItem>
<FormItem name="name" label="昵称">
<Input v-model:value="userInfoForm.name" placeholder="请输入昵称" />
</FormItem>
</Form>
</Modal>
<Modal v-model:visible="imageVisible" title="头像裁剪">
<Modal v-model:open="imageVisible" centered title="头像裁剪">
<VueCropper></VueCropper>
</Modal>
<Modal v-model:visible="mobileVisible" title="修改手机号" @ok="handleUpdateMobile">
<a-form
<Modal v-model:open="mobileVisible" centered title="修改手机号" @ok="handleUpdateMobile">
<Form
ref="formRef"
:model="form"
class="form"
:rules="formRules"
:label-col-props="{ span: 5, offset: 0 }"
:wrapper-col-props="{ span: 19, offset: 0 }"
label-align="left"
labelAlign="right"
:labelCol="{ span: 4 }"
:wrapperCol="{ span: 20 }"
>
<a-form-item required field="mobile" label="新手机号">
<a-input v-model.trim="form.mobile" size="small" placeholder="请输入新的手机号" />
</a-form-item>
<a-form-item required field="captcha" label="获取验证码">
<a-input v-model.trim="form.captcha" size="small" placeholder="请输入验证码">
<FormItem required name="mobile" label="新手机号">
<Input v-model:value="form.mobile" size="small" placeholder="请输入新的手机号" />
</FormItem>
<FormItem required name="captcha" label="获取验证码">
<Input v-model:value="form.captcha" size="small" placeholder="请输入验证码">
<template #suffix>
<span v-if="countdown <= 0" @click="sendCaptcha">发送验证码</span>
<span v-else>{{ countdown }}s</span>
</template>
</a-input>
</a-form-item>
</a-form>
</Input>
</FormItem>
</Form>
<PuzzleVerification
:show="verificationVisible"
@submit="handleVerificationSubmit"
@ -78,6 +83,7 @@
</div>
</template>
<script setup lang="ts">
import { Button, Form, FormItem, Input, Table, message, Avatar } from 'ant-design-vue';
import Container from '@/components/container.vue';
import Modal from '@/components/modal.vue';
import PuzzleVerification from '@/views/login/components/PuzzleVerification.vue';
@ -122,35 +128,32 @@ const dataSource = computed(() => {
const formRules = {
mobile: [
{
required: true,
message: '请填写手机号',
trigger: ['blur', 'change'],
},
{
validator: (value: string, callback: (error?: string) => void) => {
if (!/^1[3-9]\d{9}$/.test(value)) {
callback('手机号格式不正确');
} else {
callback();
validator: (_rule: any, value: string) => {
if (!value) {
return Promise.reject('请填写手机号');
}
if (!/^1[3-9]\d{9}$/.test(value)) {
return Promise.reject('手机号格式不正确');
}
return Promise.resolve();
},
required: true,
trigger: ['blur', 'change'],
},
],
captcha: [
{
required: true,
message: '请填写验证码',
trigger: ['blur', 'change'],
},
{
validator: (value: string, callback: (error?: string) => void) => {
if (!/^\d{6}$/.test(value)) {
callback('验证码必须是6位数字');
} else {
callback();
validator: (rule, value) => {
if (!value) {
return Promise.reject('请填写验证码');
}
if (!/^\d{6}$/.test(value)) {
return Promise.reject('验证码必须是6位数字');
}
return Promise.resolve();
},
trigger: ['blur', 'change'],
},
],
@ -203,7 +206,7 @@ function openEditMobileModal() {
async function handleSubmitUserInfo() {
await updateMyInfo(userInfoForm);
AMessage.success('修改成功!');
message.success('修改成功!');
}
async function sendCaptcha() {
@ -213,7 +216,7 @@ async function sendCaptcha() {
verificationVisible.value = true;
isSendCaptcha.value = true;
}
AMessage.error('请填写正确的手机号!');
message.error('请填写正确的手机号!');
} catch (error) {
console.log('手机号验证失败:', error);
}
@ -231,7 +234,7 @@ function beginCountdown() {
async function handleVerificationSubmit() {
await sendUpdateMobileCaptcha({ mobile: form.mobile });
AMessage.success('发送成功');
message.success('发送成功');
verificationVisible.value = false;
countdown.value = 60;
beginCountdown();
@ -239,13 +242,13 @@ async function handleVerificationSubmit() {
async function handleUpdateMobile() {
if (!isSendCaptcha.value) {
AMessage.error('请先获取验证码!');
message.error('请先获取验证码!');
return false;
}
const res = await formRef.value.validate();
if (res === true || res === undefined) {
await updateMobile(form);
AMessage.success('修改成功!');
message.success('修改成功!');
}
}

View File

@ -1,34 +0,0 @@
<!--
* @Author: 田鑫
* @Date: 2023-03-05 15:13:44
* @LastEditors: 田鑫
* @LastEditTime: 2023-03-05 15:43:18
* @Description: 模拟登录鉴权页
-->
<template>
<a-modal title="登录鉴权页" :visible="true" :footer="false">
<div w100 align-center>
<a-button w-160 @click="login" :loading="loading" type="primary">登录</a-button>
</div>
</a-modal>
</template>
<script lang="ts" setup>
const route = useRoute();
const router = useRouter();
const loading = ref(false);
function login() {
loading.value = true;
localStorage.setItem('satoken', '123asdzxc');
AMessage.success('登录鉴权成功,准备跳转');
setTimeout(() => {
loading.value = false;
router.push({ name: route.query?.redirect ? route.query?.redirect : 'dashboard' });
}, 1500);
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -1,19 +0,0 @@
<!--
* @Author: 田鑫
* @Date: 2023-03-05 14:27:21
* @LastEditors: 田鑫
* @LastEditTime: 2023-03-05 15:14:15
* @Description:
-->
<template>
<a-modal title="选择企业:" :visible="true">
<a-select v-model="enterprise" placeholder="请选择您的企业"></a-select>
</a-modal>
</template>
<script lang="ts" setup>
const enterprise = ref('');
</script>
<style lang="scss" scoped>
</style>

View File

@ -6,7 +6,7 @@
<div class="m-auto mt-24px max-w-1000px">
<Container title="推荐产品" class="container-body">
<div class="grid grid-cols-3 gap-20px">
<Product v-for="product in products" :key="product.id" :product="product" @refresh="getProductList" />
<!-- <Product v-for="product in products" :key="product.id" :product="product" @refresh="getProductList" /> -->
</div>
<NoData v-if="products.length === 0" />
</Container>
@ -20,7 +20,7 @@
</template>
<script setup lang="ts">
import Container from '@/components/container.vue';
import Product from '@/views/components/workplace/modules/product.vue';
// import Product from '@/views/components/workplace/modules/product.vue';
import Case from '@/views/components/workplace/modules/case.vue';
import { fetchProductList, fetchSuccessCaseList } from '@/api/all/index';
import { ref, onMounted } from 'vue';

View File

@ -2,16 +2,15 @@
<div class="container">
<div class="flex arco-row-justify-space-between">
<img class="avatar" :src="props.product.image" :alt="props.product.name" />
<a-tag v-if="props.product.status === Status.Enable" class="status status-enable">已开通</a-tag>
<a-tag v-if="props.product.status === Status.Disable" class="status status-disable">未开通</a-tag>
<a-tag v-if="props.product.status === Status.EXPIRED" class="status status-expired">已到期</a-tag>
<a-tag v-if="props.product.status === Status.TRIAL_ENDS" class="status status-expired">试用结束</a-tag>
<a-countdown
<Tag v-if="props.product.status === Status.Enable" class="status status-enable">已开通</Tag>
<Tag v-if="props.product.status === Status.Disable" class="status status-disable">未开通</Tag>
<Tag v-if="props.product.status === Status.EXPIRED" class="status status-expired">已到期</Tag>
<Tag v-if="props.product.status === Status.TRIAL_ENDS" class="status status-expired">试用结束</Tag>
<Countdown
v-if="props.product.status === Status.ON_TRIAL"
class="status-on-trill"
title="试用中"
:value="1000 * (props.product.expired_at ?? 0)"
:now="now()"
format="D天H时m分s秒"
/>
</div>
@ -22,47 +21,50 @@
</p>
</div>
<div class="footer flex arco-row-justify-start">
<a-button
<Button
v-if="props.product.status === Status.Enable || props.product.status === Status.ON_TRIAL"
type="primary"
size="mini"
size="small"
class="mr-8px"
@click="gotoModule(props.product.id)"
>
进入模块
</a-button>
<a-button
</Button>
<Button
v-if="props.product.status === Status.TRIAL_ENDS || props.product.status === Status.EXPIRED"
size="mini"
size="small"
type="primary"
class="mr-8px"
@click="visible = true"
>
立即购买
</a-button>
<a-button
</Button>
<Button
v-if="props.product.status === Status.ON_TRIAL"
class="mr-8px"
size="mini"
type="outline"
size="small"
type="primary"
ghost
@click="visible = true"
>
升级购买
</a-button>
<a-button
</Button>
<Button
v-if="props.product.status === Status.TRIAL_ENDS || props.product.status === Status.EXPIRED"
class="mr-8px"
size="mini"
type="outline"
size="small"
type="primary"
ghost
@click="visible = true"
>
联系客服
</a-button>
<a-popconfirm focusLock title="试用产品" content="确定试用该产品吗?" @ok="handleTrial(props.product.id)">
<a-button v-if="props.product.status === Status.Disable" size="mini" type="outline"> 免费试用7天 </a-button>
</a-popconfirm>
</Button>
<Popconfirm title="试用产品" ok-text="确定" cancel-text="取消" @confirm="handleTrial(props.product.id)">
<template #description>确定试用该产品吗</template>
<Button v-if="props.product.status === Status.Disable" size="small" type="default" ghost> 免费试用7天 </Button>
</Popconfirm>
</div>
<CustomerServiceModal v-model:visible="visible" />
<CustomerServiceModal v-model:open="visible" centered/>
</div>
</template>
@ -71,6 +73,8 @@ import { now } from '@vueuse/core';
import { trialProduct } from '@/api/all';
import { useRouter } from 'vue-router';
import CustomerServiceModal from '@/components/customer-service-modal.vue';
import { Button, message, Tag, Statistic, Popconfirm } from 'ant-design-vue';
const { Countdown } = Statistic;
import { useSidebarStore } from '@/stores/modules/side-bar';
import { useEnterpriseStore } from '@/stores/modules/enterprise';
@ -110,7 +114,7 @@ const handleTrial = async (id: any) => {
if (code === 200) {
getUserEnterpriseInfo();
AMessage.success('试用成功!');
message.success('试用成功!');
emit('refresh');
}
};
@ -174,7 +178,7 @@ const gotoModule = (menuId: number) => {
border-radius: 4px;
background: rgba(255, 245, 222, 1);
:deep(.arco-statistic-title) {
:deep(.ant-statistic-title) {
font-family: $font-family-medium;
font-weight: 400;
font-size: 12px;
@ -184,7 +188,7 @@ const gotoModule = (menuId: number) => {
padding: 0;
}
:deep(.arco-statistic-value) {
:deep(.ant-statistic-content) {
font-family: $font-family-medium;
font-weight: 400;
font-size: 10px;

View File

@ -1,20 +1,19 @@
<template>
<a-modal v-model:visible="visible" title="删除对话" width="400px" @close="onClose">
<Modal v-model:open="visible" title="删除对话" width="400px" @cancel="onClose" centered>
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认删除对话吗删除后聊天记录将不可恢复</span>
</div>
<template #footer>
<a-button size="large" @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px !bg-#f64b31 !border-none" status="danger" size="large" @click="onDelete"
>确定</a-button
>
<Button @click="onClose">取消</Button>
<Button type="primary" danger @click="onDelete">确定</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Modal, Button, message } from 'ant-design-vue';
import { deleteHistoryItem } from '@/api/all/chat';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
@ -41,7 +40,7 @@ const open = (record) => {
async function onDelete() {
const { code } = await deleteHistoryItem(conversationId.value);
if (code === 200) {
AMessage.success('删除成功');
message.success('删除成功');
emits('delete', conversationId.value);
onClose();
}

View File

@ -94,7 +94,7 @@ export default {
});
return () => (
<Drawer width={240} rootClassName="ct-history-conversation-drawer" v-model:open={open.value} onClose={onClose}>
<Drawer width={240} rootClassName="ct-history-conversation-drawer" v-model:open={open.value} onClose={onClose}>
<header class="header h-40px px-12px flex justify-between items-center">
<span class="text-12px font-400 color-#211F24 font-family-medium">历史对话</span>
<icon-close size={16} class="color-#211F24 cursor-pointer" onClick={onClose} />

View File

@ -1,18 +1,19 @@
<template>
<a-modal v-model:visible="visible" title="确定删除评论?" width="400px" @close="onClose">
<Modal v-model:open="visible" title="确定删除评论?" width="400px" @cancel="onClose" centered>
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>删除的评论将从对话中消失但仍在被引用的评论中可见</span>
</div>
<template #footer>
<a-button size="large" @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px !border-none" size="large" @click="onDelete">删除</a-button>
<Button @click="onClose">取消</Button>
<Button type="primary" danger @click="onDelete">确定</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Modal, Button } from 'ant-design-vue';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
const emits = defineEmits(['delete', 'close']);

View File

@ -1,5 +1,6 @@
<script lang="jsx">
import { Image, Spin, Button, Input, Textarea, Affix } from '@arco-design/web-vue';
import { Button, Input, Image } from 'ant-design-vue';
const { TextArea } = Input;
import TextOverTips from '@/components/text-over-tips';
import SvgIcon from '@/components/svg-icon/index.vue';
import DeleteCommentModal from './delete-comment-modal.vue';
@ -125,23 +126,23 @@ export default {
</div>
)}
<Textarea
<TextArea
ref={textAreaRef}
auto-size
class={`max-h-220px overflow-y-auto ${isReplay.value ? 'pt-38px' : ''}`}
autoSize
class={`max-h-220px overflow-y-auto textarea-box ${isReplay.value ? 'pt-38px' : ''}`}
size="large"
placeholder="输入评论"
v-model={comment.value}
v-model:value={comment.value}
onPressEnter={onComment}
/>
</div>
{comment.value && (
<div class="flex justify-end mt-12px">
<Button type="outline" class="mr-12px rounded-8px" size="medium" onClick={onClearComment}>
<Button type="primary" ghost class="cancel-btn mr-12px !rounded-8px" onClick={onClearComment}>
取消
</Button>
<Button type="primary" class="rounded-8px" size="medium" onClick={onComment}>
<Button type="primary" class="!rounded-8px" onClick={onComment}>
发送
</Button>
</div>

View File

@ -19,6 +19,9 @@
font-family: $font-family-medium;
}
}
.cancel-btn {
background-color: transparent !important;
}
.ai-text {
font-family: $font-family-medium;
font-size: 16px;
@ -30,7 +33,7 @@
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
:deep(.arco-textarea-wrapper) {
.textarea-box {
min-height: 38px;
display: flex;
border-color: transparent !important;
@ -39,14 +42,11 @@
background-color: #fff;
color: #211f24 !important;
transition: all 0.3s;
.arco-textarea-mirror,
.arco-textarea {
padding: 8px 16px !important;
}
padding: 8px 16px !important;
&:hover {
border-color: #6d4cfe !important;
}
&.arco-textarea-focus {
&:focus-within {
border-color: #6d4cfe !important;
}
}

View File

@ -1,5 +1,6 @@
<script lang="jsx">
import { Image, Spin, Button } from '@arco-design/web-vue';
import { Button } from 'ant-design-vue';
import { Spin } from 'ant-design-vue';
import AiSuggest from './components/ai-suggest/';
import { getShareWorksList, getShareWorksDetail, patchShareWorksConfirm } from '@/api/all/generationWorkshop.ts';
@ -186,7 +187,7 @@ export default {
下一条
</Button>
)}
<Button type="outline" size="large" class="mr-12px" onClick={onBackList}>
<Button type="primary" ghost size="large" class="mr-12px" onClick={onBackList}>
返回列表
</Button>
{renderConfirmBtn()}
@ -220,7 +221,7 @@ export default {
</div>
</header>
{loading.value ? (
<Spin spinning={loading.value} class="flex-1 w-full flex justify-center items-center" size={60} />
<Spin spinning={loading.value} wrapperClassName="flex-1 w-full flex justify-center items-center" size="large" />
) : (
<section class={`page-wrap relative ${isExpand.value ? 'expand' : ''}`}>
<div class="fold-box cursor-pointer" onClick={() => (isExpand.value = true)}>

View File

@ -1,6 +1,6 @@
<script lang="jsx">
import TextOverTips from '@/components/text-over-tips';
import { Image, Spin } from '@arco-design/web-vue';
import { Image, Spin } from 'ant-design-vue';
import { exactFormatTime } from '@/utils/tools';
import { handleUserHome } from '@/utils/user.ts';
@ -52,7 +52,7 @@ export default {
</header>
<section class="page-wrapper flex justify-center">
{loading.value ? (
<Spin spinning={loading.value} class="w-full flex justify-center items-center" size={60} />
<Spin spinning={loading.value} wrapperClassName="w-full flex justify-center items-center" size="large" />
) : (
<div class="explore-container">
<div class="explore-list-wrap pt-24px pb-28px">

View File

@ -1,5 +1,5 @@
<script lang="tsx">
import { Tabs, TabPane } from 'ant-design-vue';
import { Tabs, TabPane, Button } from 'ant-design-vue';
import ManuscriptList from './manuscript/list/index.vue';
import ManuscriptCheckList from './manuscript/check-list/index.vue';
import ShareManuscriptModal from '@/views/material-center/components/finished-products/manuscript/components/share-manuscript-modal/index.vue';
@ -33,17 +33,17 @@ export default defineComponent({
v-slots={{
rightExtra: () => (
<div class="flex items-center">
<a-button type="outline" size="medium" onClick={handleShareModal}>
<Button type="primary" ghost size="medium" onClick={handleShareModal}>
分享内容稿件
</a-button>
</Button>
{showManuscriptList.value && (
<a-button
<Button
type="primary"
size="medium"
class="ml-12px"
onClick={openUploadModal}
v-slots={{
icon: () => <icon-plus size="16" />,
icon: () => <icon-plus size="16" class="mr-8px" />,
default: () => '上传内容稿件',
}}
/>

View File

@ -8,42 +8,42 @@
<div class="filter-row">
<div class="filter-row-item">
<span class="label">内容稿件标题</span>
<a-space size="medium">
<a-input
v-model="query.title"
<Space size="medium">
<Input
v-model:value="query.title"
class="!w-240px"
placeholder="请输入内容稿件标题"
size="medium"
allow-clear
allowClear
@change="handleSearch"
>
<template #prefix>
<icon-search />
</template>
</a-input>
</a-space>
</Input>
</Space>
</div>
<div class="filter-row-item">
<span class="label">序号</span>
<a-space size="medium">
<a-input
v-model="query.uid"
<Space size="medium">
<Input
v-model:value="query.uid"
class="!w-160px"
placeholder="请输入序号"
size="medium"
allow-clear
allowClear
@change="handleSearch"
>
<template #prefix>
<icon-search />
</template>
</a-input>
</a-space>
</Input>
</Space>
</div>
<div class="filter-row-item" v-if="query.audit_status === AuditStatus.Pending">
<span class="label">上传时间</span>
<a-range-picker
v-model="created_at"
<DatePicker.RangePicker
v-model:value="created_at"
size="medium"
allow-clear
format="YYYY-MM-DD"
@ -54,25 +54,25 @@
<template v-if="[AuditStatus.Auditing, AuditStatus.Passed].includes(query.audit_status)">
<div class="filter-row-item">
<span class="label">审核平台</span>
<a-select
v-model="query.audit_platform"
size="medium"
<Select
v-model:value="query.audit_platform"
size="middle"
placeholder="全部"
allow-clear
allowClear
@change="handleSearch"
class="!w-160px"
>
<a-option v-for="(item, index) in PLATFORMS" :key="index" :value="item.value" :label="item.label">{{
<Option v-for="(item, index) in PLATFORMS" :key="index" :value="item.value" :label="item.label">{{
item.label
}}</a-option>
</a-select>
}}</Option>
</Select>
</div>
<div class="filter-row-item">
<span class="label">审核时间</span>
<a-range-picker
v-model="audit_started_at"
<DatePicker.RangePicker
v-model:value="audit_started_at"
size="medium"
allow-clear
allowClear
format="YYYY-MM-DD"
class="!w-280px"
@change="(value) => onDateChange(value, 'audit_started_at')"
@ -81,27 +81,31 @@
</template>
<div class="filter-row-item">
<a-button type="outline" class="mr-12px" size="medium" @click="handleSearch">
<Button type="primary" ghost class="mr-12px" size="medium" @click="handleSearch">
<template #icon>
<icon-search />
<icon-search class="mr-8px"/>
</template>
<template #default>搜索</template>
</a-button>
<a-button size="medium" @click="handleReset">
</Button>
<Button size="medium" @click="handleReset">
<template #icon>
<icon-refresh />
<icon-refresh class="mr-8px" />
</template>
<template #default>重置</template>
</a-button>
</Button>
</div>
</div>
</div>
</template>
<script setup>
import { Button, Input, Select, Space, DatePicker } from 'ant-design-vue';
const { Option } = Select;
import { defineEmits, defineProps } from 'vue';
import { PLATFORMS } from '@/views/material-center/components/finished-products/manuscript/check-list/constants';
import { AuditStatus } from '@/views/material-center/components/finished-products/constants';
import { ref, nextTick } from 'vue';
import dayjs from 'dayjs';
const props = defineProps({
query: {
@ -110,7 +114,7 @@ const props = defineProps({
},
});
const emits = defineEmits('search', 'reset', 'update:query');
const emits = defineEmits(['search', 'reset', 'update:query']);
const created_at = ref([]);
const audit_started_at = ref([]);
@ -137,6 +141,7 @@ const onDateChange = (value, type) => {
const handleReset = () => {
created_at.value = [];
audit_started_at.value = [];
emits('reset');
};
</script>

View File

@ -1,25 +1,27 @@
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
title="删除内容稿件"
width="480px"
@close="onClose"
centered
@cancel="onClose"
>
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认删除 {{ projectName }} 这个内容稿件吗</span>
</div>
<template #footer>
<a-button size="medium" @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px" status="danger" size="medium" @click="onDelete"
>确认删除</a-button
<Button size="medium" @click="onClose">取消</Button>
<Button type="primary" class="ml-16px" danger size="medium" @click="onDelete"
>确认删除</Button
>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Button, Modal, message } from 'ant-design-vue';
import { deleteWork } from '@/api/all/generationWorkshop';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
@ -48,7 +50,7 @@ const open = (record) => {
async function onDelete() {
const { code } = await deleteWork(projectId.value);
if (code === 200) {
AMessage.success('删除成功');
message.success('删除成功');
update()
onClose();
}

View File

@ -1,133 +1,117 @@
<template>
<a-table
<Table
ref="tableRef"
:data="dataSource"
row-key="id"
column-resizable
:dataSource="dataSource"
rowKey="id"
:pagination="false"
:scroll="{ x: '100%' }"
class="flex-1 manuscript-table w-100%"
bordered
:row-selection="rowSelection"
:selected-row-keys="selectedRowKeys"
@sorter-change="handleSorterChange"
@select="(selectedKeys, rowKeyValue, record) => emits('select', selectedKeys, rowKeyValue, record)"
@select-all="(check) => emits('selectAll', check)"
:rowSelection="rowSelection"
:showSorterTooltip="false"
@change="handleTableChange"
>
<template #empty>
<template #emptyText>
<NoData text="暂无稿件" />
</template>
<template #columns>
<a-table-column
v-for="column in tableColumns"
:key="column.dataIndex"
:data-index="column.dataIndex"
:fixed="column.fixed"
:width="column.width"
:min-width="column.minWidth"
:sortable="column.sortable"
:align="column.align"
ellipsis
tooltip
>
<template #title>
<div class="flex items-center">
<span class="cts mr-4px">{{ column.title }}</span>
<a-tooltip v-if="column.tooltip" :content="column.tooltip" position="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</a-tooltip>
</div>
</template>
<Column
v-for="column in tableColumns"
:key="column.dataIndex"
:dataIndex="column.dataIndex"
:fixed="column.fixed"
:width="column.width"
:minWidth="column.minWidth"
:sorter="column.sortable"
:align="column.align"
:ellipsis="true"
>
<template #title>
<span class="cts mr-4px">{{ column.title }}</span>
<Tooltip v-if="column.tooltip" :title="column.tooltip" placement="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</Tooltip>
</template>
<template v-if="column.dataIndex === 'customer_opinion'" #cell="{ record }">
<p
class="h-28px px-8px flex items-center rounded-2px w-fit"
:style="{ background: getCustomerOpinionInfo(record.customer_opinion)?.bg }"
>
<span class="cts" :class="getCustomerOpinionInfo(record.customer_opinion)?.color">{{
getCustomerOpinionInfo(record.customer_opinion)?.label ?? '-'
}}</span>
</p>
</template>
<template v-else-if="column.dataIndex === 'platform'" #cell="{ record }">
<template v-if="!PLATFORMS.find((item) => item.value === record.platform)"> - </template>
<img
v-else
width="24"
height="24"
class="rounded-4px"
:src="PLATFORMS.find((item) => item.value === record.platform)?.icon"
/>
</template>
<template v-else-if="column.dataIndex === 'compliance_level'" #cell="{ record }">
<span class="cts num !color-#6D4CFE">{{
record.ai_review?.compliance_level ? `${record.ai_review?.compliance_level}%` : '-'
}}</span>
</template>
<template v-else-if="column.dataIndex === 'title'" #cell="{ record }">
<TextOverTips :context="record.title" :line="3" class="title" @click="onDetail(record)" />
</template>
<template v-else-if="column.dataIndex === 'type'" #cell="{ record }">
<div class="flex items-center">
<img
:src="record.type === EnumManuscriptType.Image ? icon2 : icon3"
width="16"
height="16"
class="mr-4px"
/>
<span class="cts" :class="record.type === EnumManuscriptType.Image ? '!color-#25C883' : '!color-#6D4CFE'">{{
record.type === EnumManuscriptType.Image ? '图文' : '视频'
}}</span>
</div>
</template>
<template v-else-if="['uploader', 'last_modifier'].includes(column.dataIndex)" #cell="{ record }">
{{ record[column.dataIndex].name || record[column.dataIndex].mobile }}
</template>
<template
#cell="{ record }"
v-else-if="
['created_at', 'last_modified_at', 'audit_started_at', 'audit_passed_at'].includes(column.dataIndex)
"
<template v-if="column.dataIndex === 'customer_opinion'" #customRender="{ record }">
<p
class="h-28px px-8px flex items-center rounded-2px w-fit"
:style="{ background: getCustomerOpinionInfo(record.customer_opinion)?.bg }"
>
<span class="cts num">{{ exactFormatTime(record[column.dataIndex]) }}</span>
</template>
<template v-else-if="column.dataIndex === 'cover'" #cell="{ record }">
<HoverImagePreview :src="record.cover">
<a-image :width="64" :height="64" :src="record.cover" class="!rounded-6px" fit="cover">
<template #error>
<img :src="icon4" class="w-full h-full" />
</template>
</a-image>
</HoverImagePreview>
</template>
<template v-else-if="column.dataIndex === 'operation'" #cell="{ record }">
<div class="flex items-center">
<img class="mr-8px cursor-pointer" :src="icon1" width="14" height="14" @click="onDelete(record)" />
<a-button type="outline" size="mini" @click="onShare(record)" v-if="audit_status === AuditStatus.Passed"
>分享</a-button
>
<a-button
type="outline"
size="mini"
@click="onCheck(record)"
v-else-if="audit_status === AuditStatus.Pending"
>审核</a-button
>
<a-button type="outline" size="mini" @click="onScan(record)" v-else>查看</a-button>
</div>
</template>
<template v-else #cell="{ record }">
{{ formatTableField(column, record, true) }}
</template>
</a-table-column>
</template>
</a-table>
<span class="cts" :class="getCustomerOpinionInfo(record.customer_opinion)?.color">{{
getCustomerOpinionInfo(record.customer_opinion)?.label ?? '-'
}}</span>
</p>
</template>
<template v-else-if="column.dataIndex === 'platform'" #customRender="{ record }">
<template v-if="!PLATFORMS.find((item) => item.value === record.platform)"> - </template>
<img
v-else
width="24"
height="24"
class="rounded-4px"
:src="PLATFORMS.find((item) => item.value === record.platform)?.icon"
/>
</template>
<template v-else-if="column.dataIndex === 'compliance_level'" #customRender="{ record }">
<span class="cts num !color-#6D4CFE">{{
record.ai_review?.compliance_level ? `${record.ai_review?.compliance_level}%` : '-'
}}</span>
</template>
<template v-else-if="column.dataIndex === 'title'" #customRender="{ record }">
<TextOverTips :context="record.title" :line="3" class="title" @click="onDetail(record)" />
</template>
<template v-else-if="column.dataIndex === 'type'" #customRender="{ record }">
<div class="flex items-center">
<img :src="record.type === EnumManuscriptType.Image ? icon2 : icon3" width="16" height="16" class="mr-4px" />
<span class="cts" :class="record.type === EnumManuscriptType.Image ? '!color-#25C883' : '!color-#6D4CFE'">{{
record.type === EnumManuscriptType.Image ? '图文' : '视频'
}}</span>
</div>
</template>
<template v-else-if="['uploader', 'last_modifier'].includes(column.dataIndex)" #customRender="{ record }">
{{ record[column.dataIndex].name || record[column.dataIndex].mobile }}
</template>
<template
#customRender="{ record }"
v-else-if="['created_at', 'last_modified_at', 'audit_started_at', 'audit_passed_at'].includes(column.dataIndex)"
>
<span class="cts num">{{ exactFormatTime(record[column.dataIndex]) }}</span>
</template>
<template v-else-if="column.dataIndex === 'cover'" #customRender="{ record }">
<HoverImagePreview :src="record.cover">
<ImgLazyLoad :width="64" :height="64" :src="record.cover" class="!rounded-6px" />
</HoverImagePreview>
</template>
<template v-else-if="column.dataIndex === 'operation'" #customRender="{ record }">
<div class="flex items-center">
<img class="mr-8px cursor-pointer" :src="icon1" width="14" height="14" @click="onDelete(record)" />
<Button type="primary" ghost size="small" @click="onShare(record)" v-if="audit_status === AuditStatus.Passed"
>分享</Button
>
<Button
type="primary"
ghost
size="small"
@click="onCheck(record)"
v-else-if="audit_status === AuditStatus.Pending"
>审核</Button
>
<Button type="primary" ghost size="small" @click="onScan(record)" v-else>查看</Button>
</div>
</template>
<template v-else #customRender="{ record }">
{{ formatTableField(column, record, true) }}
</template>
</Column>
</Table>
<ShareModal ref="shareModalRef" />
</template>
<script setup>
import { ref } from 'vue';
import { Button, Tooltip, Table, Image } from 'ant-design-vue';
const { Column } = Table;
import { formatTableField, exactFormatTime } from '@/utils/tools';
import { EnumManuscriptType } from '@/views/material-center/components/finished-products/manuscript/list/constants';
import { patchWorkAuditsAudit } from '@/api/all/generationWorkshop';
@ -139,16 +123,17 @@ import { AuditStatus } from '@/views/material-center/components/finished-product
import { slsWithCatch } from '@/utils/stroage.ts';
import TextOverTips from '@/components/text-over-tips';
import TextOverTips from '@/components/text-over-tips'
import ImgLazyLoad from "@/components/img-lazy-load";
import ShareModal from '@/views/material-center/components/finished-products/manuscript/components/share-manuscript-modal/share-modal.vue';
import HoverImagePreview from '@/components/hover-image-preview';
import icon1 from '@/assets/img/media-account/icon-delete.png';
import icon2 from '@/assets/img/creative-generation-workshop/icon-photo.png';
import icon3 from '@/assets/img/creative-generation-workshop/icon-video.png';
import icon4 from '@/assets/img/error-img.png';
// import icon4 from '@/assets/img/error-img.png';
const emits = defineEmits([ 'sorterChange', 'delete', 'select', 'selectAll']);
const emits = defineEmits(['sorterChange', 'delete', 'select', 'selectAll']);
const router = useRouter();
const props = defineProps({
@ -160,10 +145,6 @@ const props = defineProps({
type: Array,
default: () => [],
},
rowSelection: {
type: Array,
default: () => [],
},
selectedRowKeys: {
type: Array,
default: () => [],
@ -176,8 +157,20 @@ const props = defineProps({
const tableRef = ref(null);
const shareModalRef = ref(null);
const handleSorterChange = (column, order) => {
emits('sorterChange', column, order === 'ascend' ? 'asc' : 'desc');
const handleTableChange = (pagination, filters, sorter) => {
if (sorter && sorter.field) {
emits('sorterChange', sorter.field, sorter.order === 'ascend' ? 'asc' : 'desc');
}
};
const rowSelection = {
selectedRowKeys: computed(() => props.selectedRowKeys),
onSelect: (record, selected) => {
emits('select', record, selected);
},
onSelectAll: (selected) => {
emits('selectAll', selected);
},
};
const onDelete = (item) => {
emits('delete', item);

View File

@ -219,7 +219,7 @@ export const INITIAL_QUERY = {
title: '',
created_at: [],
audit_started_at: [],
audit_platform: '',
audit_platform: undefined,
sort_column: undefined,
sort_order: undefined,
};

View File

@ -13,28 +13,29 @@
class="flex justify-end mb-12px"
v-if="[AuditStatus.Pending, AuditStatus.Auditing].includes(query.audit_status)"
>
<a-button
type="outline"
<Button
type="primary"
ghost
class="w-fit"
size="medium"
@click="handleBatchCheck"
v-if="query.audit_status === AuditStatus.Pending"
>批量审核</a-button
>批量审核</Button
>
<a-button
type="outline"
<Button
type="primary"
ghost
class="w-fit"
size="medium"
@click="handleBatchView"
v-if="query.audit_status === AuditStatus.Auditing"
>批量查看</a-button
>批量查看</Button
>
</div>
<ManuscriptCheckTable
:key="query.audit_status"
:tableColumns="tableColumns"
:rowSelection="rowSelection"
:selectedRowKeys="selectedRowKeys"
:dataSource="dataSource"
:audit_status="query.audit_status"
@ -44,16 +45,15 @@
@selectAll="handleSelectAll"
/>
<div v-if="pageInfo.total > 0" class="pagination-row">
<a-pagination
<Pagination
:total="pageInfo.total"
size="mini"
show-total
show-jumper
show-page-size
size="small"
:showTotal="(total, range) => `共 ${total} 条`"
showSizeChanger
showQuickJumper
:current="pageInfo.page"
:page-size="pageInfo.page_size"
:pageSize="pageInfo.page_size"
@change="onPageChange"
@page-size-change="onPageSizeChange"
/>
</div>
</div>
@ -62,7 +62,7 @@
</div>
</template>
<script lang="jsx" setup>
import { Button, Message as AMessage } from '@arco-design/web-vue';
import { Button, Pagination, message } from 'ant-design-vue';
import FilterBlock from './components/filter-block';
import ManuscriptCheckTable from './components/manuscript-check-table';
import DeleteManuscriptModal from './components/manuscript-check-table/delete-manuscript-modal.vue';
@ -83,22 +83,16 @@ const props = defineProps({
const {
dataSource,
pageInfo,
rowSelection,
onPageChange,
onPageSizeChange,
resetPageInfo,
selectedRowKeys,
selectedRows,
handleSelect,
handleSelectAll,
DEFAULT_PAGE_INFO,
} = useTableSelectionWithPagination({
onPageChange: () => {
getData();
},
onPageSizeChange: () => {
getData();
},
});
const router = useRouter();
const tableColumns = ref([]);
@ -140,7 +134,7 @@ const handleSorterChange = (column, order) => {
};
const handleBatchCheck = () => {
if (!selectedRows.value.length) {
AMessage.warning('请选择需审核的内容稿件');
message.warning('请选择需审核的内容稿件');
return;
}
@ -151,7 +145,7 @@ const handleBatchCheck = () => {
};
const handleBatchView = () => {
if (!selectedRows.value.length) {
AMessage.warning('请选择需查看的内容稿件');
message.warning('请选择需查看的内容稿件');
return;
}
@ -175,7 +169,6 @@ const handleDelete = (item) => {
deleteManuscriptModalRef.value?.open({ id, name: `${title}` });
};
watch(
() => props.audit_status,
(newVal) => {

View File

@ -1,9 +1,10 @@
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
:title="action === 'exit' ? '退出审核' : '切换内容稿件'"
width="480px"
@close="onClose"
centered
@cancel="onClose"
>
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
@ -14,16 +15,17 @@
}}</span>
</div>
<template #footer>
<a-button size="medium" @click="onClose">继续编辑</a-button>
<a-button type="primary" class="ml-8px" size="medium" @click="onConfirm">
<Button size="medium" @click="onClose">继续编辑</Button>
<Button type="primary" class="ml-8px" size="medium" @click="onConfirm">
{{ action === 'exit' ? '确认退出' : '确认切换' }}
</a-button>
</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Button, Modal } from 'ant-design-vue';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
import { useRouter } from 'vue-router';

View File

@ -1,10 +1,11 @@
<template>
<a-modal
v-model:visible="visible"
<Modal
v-model:open="visible"
title="提示"
width="480px"
@close="onClose"
modal-class="upload-success11-modal"
centered
wrapClassName="upload-success11-modal"
:footer="null"
>
<div class="flex items-center flex-col justify-center">
@ -13,14 +14,12 @@
<p class="text-14px lh-22px font-400 color-#737478 ld">想让内容更抓眼球更吸流量吗</p>
<p class="text-14px lh-22px font-400 color-#737478 ld">试试内容稿件分析功能吧</p>
</div>
<!-- <template #footer>
<a-button type="primary" class="ml-8px" size="medium" @click="onConfirm">内容稿件分析</a-button>
</template> -->
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Button, Modal } from 'ant-design-vue';
import icon1 from '@/assets/img/media-account/icon-feedback-success.png';
const router = useRouter();

View File

@ -1,5 +1,5 @@
<script lang="jsx">
import { Drawer, Image } from '@arco-design/web-vue';
import { Drawer, Image } from 'ant-design-vue';
import TextOverTips from '@/components/text-over-tips';
import icon1 from '@/assets/img/error-img.png';
@ -32,12 +32,12 @@ export default {
return () => (
<Drawer
title="审核列表"
visible={visible.value}
v-model:open={visible.value}
width={420}
class="check-list-drawer-xt"
footer={false}
header={false}
onCancel={onClose}
rootClassName="check-list-drawer-xt"
footer={null}
closable={false}
onClose={onClose}
>
<div class="flex justify-between items-center h-56px px-24px">
<div class="flex items-center">
@ -61,13 +61,12 @@ export default {
height={48}
preview={false}
src={item.cover}
class="!rounded-4px mr-8px"
fit="cover"
class="!rounded-4px"
v-slots={{
error: () => <img src={icon1} class="w-full h-full" />,
}}
/>
<div class="flex-1 overflow-hidden flex flex-col items-start">
<div class="flex-1 overflow-hidden flex flex-col items-start ml-8px">
<TextOverTips context={item.title} class={`cts !color-#211F24 title mb-4px !text-14px`} />
<p class="cts !text-14px">{`合规程度:${
item.ai_review?.compliance_level ? `${item.ai_review?.compliance_level}%` : '-'

View File

@ -1,43 +1,44 @@
.check-list-drawer-xt {
.arco-drawer-mask {
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.15);
.ant-drawer-mask {
background-color: transparent;
}
.arco-drawer {
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.15);
.arco-drawer-body {
overflow: hidden;
display: flex;
flex-direction: column;
padding: 0 0 24px;
.cts {
color: var(--Text-1, #939499);
.ant-drawer-header {
display: none;
}
.ant-drawer-body {
overflow: hidden;
display: flex;
flex-direction: column;
padding: 0 0 24px;
.cts {
color: var(--Text-1, #939499);
font-family: $font-family-regular;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 24px;
&.bold {
color: var(--Text-1, #211f24);
font-family: $font-family-medium;
}
font-family: $font-family-regular;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 24px;
&.bold {
color: var(--Text-1, #211f24);
font-family: $font-family-medium;
}
.card-item {
cursor: pointer;
border: 1px solid transparent;
transition: all;
&:hover {
background-color: #e6e6e8;
}
&:not(:last-child) {
margin-bottom: 12px;
}
&.active {
border-color: #6d4cfe;
background-color: #f0edff;
:deep(.overflow-text) {
font-family: $font-family-medium !important;
}
}
.card-item {
cursor: pointer;
border: 1px solid transparent;
transition: all;
&:hover {
background-color: #e6e6e8;
}
&:not(:last-child) {
margin-bottom: 12px;
}
&.active {
border-color: #6d4cfe;
background-color: #f0edff;
:deep(.overflow-text) {
font-family: $font-family-medium !important;
}
}
}

View File

@ -14,10 +14,10 @@ export const TAB_LIST = [
label: '文本',
value: enumTab.TEXT,
},
// {
// label: '图片',
// value: enumTab.IMAGE,
// },
{
label: '图片',
value: enumTab.IMAGE,
},
];
export enum Enum_Level {

View File

@ -24,6 +24,8 @@
</template>
<script setup lang="ts">
import { Input } from 'ant-design-vue';
const {TextArea} = Input
import { ref, computed, watch, defineProps, defineEmits, onMounted, onUnmounted } from 'vue';
import { escapeRegExp } from './constants';

View File

@ -1,23 +1,13 @@
<script lang="jsx">
import axios from 'axios';
import { Swiper, SwiperSlide } from 'swiper/vue';
import { Button, Form, Input, FormItem, Tabs, message, Image, Upload, Spin } from 'ant-design-vue';
import { IconLoading } from '@arco-design/web-vue/es/icon';
import {
Image,
Form,
FormItem,
Input,
Textarea,
Button,
Tabs,
Upload,
TabPane,
Spin,
Message as AMessage,
} from '@arco-design/web-vue';
import TextOverTips from '@/components/text-over-tips';
import HighlightTextarea from './highlight-textarea';
const { TabPane } = Tabs;
import 'swiper/css';
import 'swiper/css/navigation';
import { Navigation } from 'swiper/modules';
@ -87,7 +77,7 @@ export default {
const onAgainCheck = () => {
if (!isTextTab.value && !props.modelValue.files?.length) {
AMessage.warning('请先上传需审核图片');
message.warning('请先上传需审核图片');
return;
}
emit('againCheck');
@ -99,15 +89,7 @@ export default {
activeTab.value = key;
};
const validate = () => {
return new Promise((resolve, reject) => {
formRef.value?.validate((errors) => {
if (errors) {
reject();
} else {
resolve();
}
});
});
return formRef.value?.validate();
};
const reset = () => {
formRef.value?.resetFields?.();
@ -139,24 +121,19 @@ export default {
<Upload
ref={uploadRef}
action="/"
draggable
class="w-fit"
custom-request={(option) => uploadImage(option, action)}
customRequest={(option) => uploadImage(option, action)}
accept=".jpg,.jpeg,.png,.gif,.webp"
show-file-list={false}
showUploadList={false}
multiple
>
{{
'upload-button': () => <UploadBtn />,
}}
<UploadBtn />
</Upload>
);
};
const uploadImage = async (option, action = 'upload') => {
const {
fileItem: { file },
} = option;
const { file } = option;
const { name, size, type } = file;
const response = await getImagePreSignedUrl({ suffix: getFileExtension(name) });
@ -190,11 +167,11 @@ export default {
const renderFooterRow = () => {
return (
<>
<Button class="mr-12px" size="medium" onClick={onAgainCheck} disabled={isDisabled.value}>
<Button class="mr-12px" onClick={onAgainCheck} disabled={isDisabled.value}>
再次审核
</Button>
{isTextTab.value ? (
<Button size="medium" type="outline" class="w-123px check-btn" onClick={onAiReplace} disabled={isDisabled.value}>
<Button type="primary" ghost class="w-123px check-btn" onClick={onAiReplace} disabled={isDisabled.value}>
{aiReplaceLoading.value ? (
<>
<IconLoading size={14} />
@ -210,7 +187,7 @@ export default {
) : (
<div class="w-88px">
{renderUpload(
<Button size="medium" type="outline">
<Button type="primary" ghost>
图片替换
</Button>,
'replaceImage',
@ -223,17 +200,17 @@ export default {
const renderTextForm = () => {
return (
<Form ref={formRef} model={props.modelValue} rules={FORM_RULES} layout="vertical" auto-label-width>
<FormItem label="标题" field="title" required>
<FormItem label="标题" name="title" required>
<Input
v-model={props.modelValue.title}
v-model:value={props.modelValue.title}
placeholder="请输入标题"
size="large"
maxLength={30}
show-word-limit
maxlength={30}
showCount
disabled={isDisabled.value}
/>
</FormItem>
<FormItem label="作品描述" field="content" class="flex-1 content-form-item">
<FormItem label="作品描述" name="content" class="flex-1 content-form-item">
<HighlightTextarea
v-model={props.modelValue.content}
disabled={isDisabled.value}
@ -409,9 +386,9 @@ export default {
<div class="right-box">
<p class="cts bold !text-16px !lh-24px !color-#211F24 mb-16px">审核结果</p>
<Spin
loading={true}
spinning={true}
tip={`${isTextTab.value ? '文本' : '图片'}检测中`}
size={72}
size="large"
class="h-298px !flex flex-col justify-center items-center"
/>
</div>
@ -421,7 +398,7 @@ export default {
<div class="right-box">
<p class="cts bold !text-16px !lh-24px !color-#211F24 mb-16px">审核结果</p>
{props.getDataLoading ? (
<Spin loading={true} size={72} class="h-298px !flex justify-center items-center" />
<Spin spinning={true} size="large" class="h-298px !flex justify-center items-center" />
) : (
renderCheckSuccessBox()
)}
@ -440,22 +417,20 @@ export default {
<div class="h-full w-full px-24px pt-16px pb-24px content-wrap flex">
<div class="flex-2 left-box mr-24px flex flex-col">
<div class="flex-1 mb-12px rounded-8px border-1px pt-8px flex flex-col pb-16px bg-#F7F8FA border-#E6E6E8 border-solid">
<Tabs v-model={activeTab.value} onTabClick={handleTabClick} class="mb-16px">
<Tabs activeKey={activeTab.value} onChange={handleTabClick} class="mb-16px">
{TAB_LIST.map((item) => (
<TabPane
key={item.value}
v-slots={{
title: () => (
<div class="flex items-center relative">
<span>{item.label}</span>
{
// activeTab.value === item.value && aiReview.value?.violation_items.length > 0 && (
// <icon-exclamation-circle-fill size={14} class="color-#F64B31 absolute right--10px top-0" />
// )
}
</div>
),
}}
tab={
<div class="flex items-center relative">
<span>{item.label}</span>
{
// activeTab.value === item.value && aiReview.value?.violation_items.length > 0 && (
// <icon-exclamation-circle-fill size={14} class="color-#F64B31 absolute right--10px top-0" />
// )
}
</div>
}
/>
))}
</Tabs>

View File

@ -23,43 +23,32 @@
}
.left-box {
:deep(.arco-tabs) {
.arco-tabs-nav {
.arco-tabs-tab {
height: 40px;
// padding: 0 8px;
margin: 0 16px;
}
&::before {
display: none;
}
}
.arco-tabs-content {
display: none;
}
}
:deep(.arco-form) {
:deep(.ant-form) {
height: 100%;
display: flex;
flex-direction: column;
.arco-form-item {
.ant-form-item {
margin-bottom: 24px;
.arco-form-item-label-col {
.arco-form-item-label {
.ant-form-item-label-col {
.ant-form-item-label {
color: #939499;
}
}
}
.content-form-item {
margin-bottom: 0;
display: flex;
flex-direction: column;
.arco-form-item-wrapper-col {
flex: 1;
.arco-form-item-content-wrapper,
.arco-form-item-content,
.arco-textarea-wrapper {
height: 100%;
.ant-row {
height: 100%;
display: flex;
flex-direction: column;
.ant-form-item-control-input {
flex: 1;
.ant-form-item-control-input-content,
.ant-form-item-control-input-content,
.ant-textarea-wrapper {
height: 100%;
}
}
}
}

View File

@ -1,5 +1,5 @@
<script lang="jsx">
import { Image } from '@arco-design/web-vue';
import { Image } from 'ant-design-vue';
import { Swiper, SwiperSlide } from 'swiper/vue';
import TextOverTips from '@/components/text-over-tips';
@ -56,13 +56,13 @@ export default {
height={48}
preview={false}
src={item.cover}
class="!rounded-4px mr-8px"
class="!rounded-4px"
fit="cover"
v-slots={{
error: () => <img src={icon1} class="w-full h-full" />,
}}
/>
<div class="flex-1 overflow-hidden flex flex-col items-start">
<div class="flex-1 overflow-hidden flex flex-col items-start ml-8px">
<TextOverTips context={item.title} class={`cts !color-#211F24 title mb-4px`} />
<p class="cts">{`合规程度:${item.ai_review?.compliance_level ? `${item.ai_review?.compliance_level}%` : '-'}`}</p>
</div>

View File

@ -1,5 +1,6 @@
<script lang="jsx">
import { Button, Message as AMessage, Spin } from '@arco-design/web-vue';
import { Button, message } from 'ant-design-vue';
import { Spin } from 'ant-design-vue';
import CancelCheckModal from './cancel-check-modal.vue';
import CheckSuccessModal from './check-success-modal.vue';
import HeaderCard from './components/header-card';
@ -135,14 +136,14 @@ export default {
};
const onSave = async () => {
if (!selectCardInfo.value.title) {
AMessage.warning('标题不能为空');
message.warning('标题不能为空');
}
contentCardRef.value?.validate().then(async () => {
const { code, data } = await putWorkAuditsUpdate(selectCardInfo.value);
if (code === 200) {
isSaved.value = true;
AMessage.success('当前内容稿件已保存');
message.success('当前内容稿件已保存');
}
});
};
@ -181,13 +182,13 @@ export default {
const renderFooterRow = () => {
return (
<>
<Button size="medium" type="outline" class="mr-12px" onClick={onExit}>
<Button type="primary" ghost class="mr-12px" onClick={onExit}>
退出
</Button>
<Button size="medium" type="outline" class="mr-12px" onClick={onSave}>
<Button type="primary" ghost class="mr-12px" onClick={onSave}>
保存
</Button>
<Button type="primary" size="medium" onClick={onSubmit} loading={submitLoading.value}>
<Button type="primary" onClick={onSubmit} loading={submitLoading.value}>
{submitLoading.value ? '通过审核中...' : '通过审核'}
</Button>
</>

View File

@ -1,5 +1,5 @@
<template>
<a-form-item field="files">
<FormItem name="files">
<template #label>
<div class="flex items-center">
<span class="cts !color-#211F24 mr-4px">图片</span>
@ -23,32 +23,31 @@
/>
</div>
</div>
<a-upload
<Upload
v-if="files.length < 18"
ref="uploadRef"
action="/"
draggable
:custom-request="(option) => emit('upload', option)"
:customRequest="(option) => emit('upload', option)"
accept=".jpg,.jpeg,.png,.gif,.webp"
:show-file-list="false"
:showUploadList="false"
multiple
class="!flex !items-center"
:limit="18 - files.length"
>
<template #upload-button>
<template #default>
<div class="upload-box">
<icon-plus size="14" class="mb-16px color-#3C4043" />
<span class="cts !color-#211F24">上传图片</span>
</div>
</template>
</a-upload>
</Upload>
</VueDraggable>
</div>
</a-form-item>
</FormItem>
</template>
<script setup>
import { VueDraggable } from 'vue-draggable-plus';
import { FormItem, Upload } from 'ant-design-vue';
const props = defineProps({
files: {

View File

@ -1,6 +1,6 @@
<script lang="jsx">
import axios from 'axios';
import { Form, FormItem, Input, Textarea, Upload, Message as AMessage, Button } from '@arco-design/web-vue';
import { Button, Form, FormItem, Input, message, Upload } from 'ant-design-vue';
// import CommonSelect from '@/components/common-select';
// import { VueDraggable } from 'vue-draggable-plus';
import TextOverTips from '@/components/text-over-tips';
@ -11,6 +11,8 @@ import { formatFileSize, getVideoInfo, formatDuration, formatUploadSpeed } from
import { EnumManuscriptType } from '@/views/material-center/components/finished-products/manuscript/list/constants.ts';
import { getImagePreSignedUrl, getVideoPreSignedUrl } from '@/api/all/common';
const { TextArea } = Input;
// import icon1 from '@/assets/img/creative-generation-workshop/icon-close.png';
// 表单验证规则
@ -128,9 +130,7 @@ export default {
formData.value.videoInfo.uploadStatus = ENUM_UPLOAD_STATUS.UPLOADING;
emit('updateVideoInfo', formData.value.videoInfo);
const {
fileItem: { file },
} = option;
const { file } = option;
setVideoInfo(file);
const response = await getVideoPreSignedUrl({ suffix: getFileExtension(file.name) });
@ -161,13 +161,11 @@ export default {
};
// 文件上传处理
const uploadImage = async (option) => {
const {
fileItem: { file },
} = option;
const { file } = option;
// 验证文件数量
if (formData.value.files?.length >= 18) {
AMessage.error('最多只能上传18张图片');
message.error('最多只能上传18张图片');
return;
}
const { name, size, type } = file;
@ -189,15 +187,7 @@ export default {
};
const validate = () => {
return new Promise((resolve, reject) => {
formRef.value?.validate((errors) => {
if (errors) {
reject(formData.value);
} else {
resolve();
}
});
});
return formRef.value?.validate();
};
const resetForm = () => {
@ -211,25 +201,18 @@ export default {
<Upload
ref={uploadRef}
action="/"
draggable
custom-request={uploadVideo}
customRequest={uploadVideo}
accept=".mp4,.mov,.avi,.flv,.wmv"
show-file-list={false}
showUploadList={false}
>
{{
'upload-button': () => {
if (formData.value.videoInfo.uploadStatus === ENUM_UPLOAD_STATUS.DEFAULT) {
return (
<div class="upload-box">
<icon-plus size="14" class="mb-16px color-#3C4043" />
<span class="cts !color-#211F24">上传视频</span>
</div>
);
} else {
return <Button type="text">替换视频</Button>;
}
},
}}
{formData.value.videoInfo.uploadStatus === ENUM_UPLOAD_STATUS.DEFAULT ? (
<div class="upload-box">
<icon-plus size="14" class="mb-16px color-#3C4043" />
<span class="cts !color-#211F24">上传视频</span>
</div>
) : (
<Button type="text">替换视频</Button>
)}
</Upload>
);
};
@ -238,7 +221,7 @@ export default {
const isEnd = formData.value.videoInfo.uploadStatus === ENUM_UPLOAD_STATUS.END;
return (
<FormItem
field="files"
name="files"
v-slots={{
label: () => (
<div class="flex items-center">
@ -315,30 +298,31 @@ export default {
return () => (
<Form ref={formRef} model={formData.value} rules={props.rules} layout="vertical" auto-label-width>
<FormItem label="标题" field="title" required>
<FormItem label="标题" name="title" required>
<Input
v-model={formData.value.title}
v-model:value={formData.value.title}
onInput={() => {
console.log('onInput');
onChange();
emit('reValidate');
}}
placeholder="请输入标题"
size="large"
class="!w-500px"
maxLength={30}
show-word-limit
maxlength={30}
showCount
/>
</FormItem>
<FormItem label="作品描述" field="content">
<Textarea
v-model={formData.value.content}
<FormItem label="作品描述" name="content">
<TextArea
v-model:value={formData.value.content}
onInput={onChange}
placeholder="请输入作品描述"
size="large"
class="textarea-box !w-784px"
show-word-limit
max-length={1000}
showCount
maxlength={1000}
/>
</FormItem>
{isVideo.value ? (
@ -352,7 +336,7 @@ export default {
/>
)}
{/* <FormItem label="所属项目" field="project_ids">
{/* <FormItem label="所属项目" name="project_ids">
<CommonSelect
v-model={formData.value.project_ids}
onChange={() => emit('change')}

View File

@ -31,8 +31,6 @@
}
}
.textarea-box {
:deep(.arco-textarea) {
height: 140px;
max-height: 298px;
}
height: 140px;
max-height: 298px;
}

View File

@ -1,5 +1,5 @@
export const INITIAL_FORM = {
audit_status: '',
audit_status: undefined,
sort_column: undefined,
sort_order: undefined,
};

View File

@ -1,22 +1,15 @@
<script lang="jsx">
import {
Input,
Table,
Modal,
TableColumn,
Checkbox,
Pagination,
Button,
Tooltip,
Notification,
} from '@arco-design/web-vue';
import { Button, Modal, Tooltip, Table, Pagination } from 'ant-design-vue';
import CommonSelect from '@/components/common-select';
import TextOverTips from '@/components/text-over-tips';
import ShareModal from '@/views/material-center/components/finished-products/manuscript/components/share-manuscript-modal/share-modal';
import { INITIAL_FORM, TABLE_COLUMNS } from './constants';
import { formatTableField, exactFormatTime } from '@/utils/tools';
import { CHECK_STATUS, EnumManuscriptType } from '@/views/material-center/components/finished-products/manuscript/list/constants';
import {
CHECK_STATUS,
EnumManuscriptType,
} from '@/views/material-center/components/finished-products/manuscript/list/constants';
import { useTableSelectionWithPagination } from '@/hooks/useTableSelectionWithPagination';
import { getWorksPage, getWriterLinksGenerate } from '@/api/all/generationWorkshop.ts';
@ -31,8 +24,6 @@ export default {
dataSource,
pageInfo,
onPageChange,
onPageSizeChange,
rowSelection,
handleSelect,
handleSelectAll,
DEFAULT_PAGE_INFO,
@ -40,9 +31,6 @@ export default {
onPageChange: () => {
getData();
},
onPageSizeChange: () => {
getData();
},
});
const visible = ref(false);
const query = ref(cloneDeep(INITIAL_FORM));
@ -86,42 +74,44 @@ export default {
reset();
};
const renderColumn = () => {
return TABLE_COLUMNS.map((column) => (
<TableColumn
key={column.dataIndex}
data-index={column.dataIndex}
fixed={column.fixed}
width={column.width}
min-width={column.minWidth}
sortable={column.sortable}
align={column.align}
ellipsis
tooltip
v-slots={{
title: () => (
<div class="flex items-center">
<span class="cts mr-4px">{column.title}</span>
{column.tooltip && (
<Tooltip content={column.tooltip} position="top">
<IconQuestionCircle class="tooltip-icon color-#737478" size={16} />
</Tooltip>
)}
</div>
),
cell: ({ record }) => renderCell(record),
}}
/>
));
};
// const renderColumn = () => {
// return TABLE_COLUMNS.map((column) => (
// <TableColumn
// key={column.dataIndex}
// data-index={column.dataIndex}
// fixed={column.fixed}
// width={column.width}
// min-width={column.minWidth}
// sortable={column.sortable}
// align={column.align}
// ellipsis
// tooltip
// v-slots={{
// title: () => (
// <>
// <span class="cts mr-4px">{column.title}</span>
// {column.tooltip && (
// <Tooltip title={column.tooltip} placement="top">
// <IconQuestionCircle class="tooltip-icon color-#737478" size={16} />
// </Tooltip>
// )}
// </>
// ),
// cell: ({ record }) => renderCell(record),
// }}
// />
// ));
// };
const onShare = () => {
shareModalRef.value?.open(selectedRowKeys.value);
};
const handleSorterChange = (column, order) => {
query.value.sort_column = column;
query.value.sort_order = order === 'ascend' ? 'asc' : 'desc';
reload();
const handleSorterChange = (pagination, filters, sorter) => {
if (sorter && !Array.isArray(sorter) && sorter.columnKey) {
query.value.sort_column = sorter.columnKey;
query.value.sort_order = sorter.order === 'ascend' ? 'asc' : 'desc';
reload();
}
};
const getStatusInfo = (audit_status) => {
return CHECK_STATUS.find((v) => v.id === audit_status) ?? {};
@ -132,12 +122,13 @@ export default {
return () => (
<>
<Modal
v-model:visible={visible.value}
v-model:open={visible.value}
title="分享内容稿件"
width="920px"
onClose={onClose}
unmount-on-close
modal-class="share-manuscript-modal"
onCancel={onClose}
centered
destroyOnClose
wrapClassName="share-manuscript-modal"
v-slots={{
footer: () => (
<div class="flex justify-between w-full items-center">
@ -145,16 +136,8 @@ export default {
已选择 <span class="cts color-#211F24 bold">{selectedRows.value.length}</span>
</p>
<div class="flex items-center">
<Button size="medium" onClick={onClose}>
取消
</Button>
<Button
type="primary"
class="ml-16px"
size="medium"
onClick={onShare}
disabled={!selectedRows.value.length}
>
<Button onClick={onClose}>取消</Button>
<Button type="primary" class="ml-16px" onClick={onShare} disabled={!selectedRows.value.length}>
分享
</Button>
</div>
@ -176,109 +159,99 @@ export default {
</div>
<Table
ref={tableRef}
data={dataSource.value}
row-key="id"
column-resizable
row-selection={rowSelection.value}
selected-keys={selectedRowKeys.value}
dataSource={dataSource.value}
rowKey="id"
rowSelection={{
selectedRowKeys: selectedRowKeys.value,
onSelect: handleSelect,
onSelectAll: handleSelectAll,
}}
pagination={false}
scroll={{ x: '100%', y: '100%' }}
class="overflow-hidden"
bordered
onSorterChange={handleSorterChange}
onSelect={handleSelect}
onSelectAll={handleSelectAll}
showSorterTooltip={false}
onChange={handleSorterChange}
v-slots={{
empty: () => <NoData />,
columns: () => (
<>
{TABLE_COLUMNS.map((column) => (
<TableColumn
key={column.dataIndex}
data-index={column.dataIndex}
fixed={column.fixed}
width={column.width}
min-width={column.minWidth}
sortable={column.sortable}
align={column.align}
ellipsis
tooltip
v-slots={{
title: () => (
<div class="flex items-center">
<span class="cts mr-4px bold color-#211F24">{column.title}</span>
{column.tooltip && (
<Tooltip content={column.tooltip} position="top">
<IconQuestionCircle class="tooltip-icon color-#737478" size={16} />
</Tooltip>
)}
</div>
),
cell: ({ record }) => {
if (column.dataIndex === 'audit_status') {
return (
<div
class="flex items-center w-fit h-24px px-8px rounded-2px"
style={{ backgroundColor: getStatusInfo(record.audit_status).backgroundColor }}
>
<span class="cts s1 bold" style={{ color: getStatusInfo(record.audit_status).color }}>
{getStatusInfo(record.audit_status).name}
</span>
</div>
);
} else if (column.dataIndex === 'title') {
return <TextOverTips context={record.title} />;
} else if (column.dataIndex === 'type') {
return (
<div class="flex items-center">
<img
src={record.type === EnumManuscriptType.Image ? icon2 : icon3}
width="16"
height="16"
class="mr-4px"
/>
<span
class={`cts !text-14px !lh-22px ${
record.type === EnumManuscriptType.Image ? '!color-#25C883' : '!color-#6D4CFE'
}`}
>
{record.type === EnumManuscriptType.Image ? '图文' : '视频'}
</span>
</div>
);
} else if (column.dataIndex === 'last_modified_at') {
return (
<span class="cts num">
{exactFormatTime(
record.last_modified_at,
'YYYY-MM-DD HH:mm:ss',
'YYYY-MM-DD HH:mm:ss',
)}
</span>
);
} else {
return formatTableField(column, record, true);
}
},
}}
/>
))}
</>
),
emptyText: () => <NoData />,
}}
/>
>
{TABLE_COLUMNS.map((column) => (
<Table.Column
key={column.dataIndex}
dataIndex={column.dataIndex}
fixed={column.fixed}
width={column.width}
minWidth={column.minWidth}
sorter={column.sortable}
align={column.align}
ellipsis
title={() => (
<>
<span class="cts mr-4px bold color-#211F24">{column.title}</span>
{column.tooltip && (
<Tooltip title={column.tooltip} placement="top">
<IconQuestionCircle class="tooltip-icon color-#737478" size={16} />
</Tooltip>
)}
</>
)}
customRender={({ record }) => {
if (column.dataIndex === 'audit_status') {
return (
<div
class="flex items-center w-fit h-24px px-8px rounded-2px"
style={{ backgroundColor: getStatusInfo(record.audit_status).backgroundColor }}
>
<span class="cts s1 bold" style={{ color: getStatusInfo(record.audit_status).color }}>
{getStatusInfo(record.audit_status).name}
</span>
</div>
);
} else if (column.dataIndex === 'title') {
return <TextOverTips context={record.title} class="!text-12px" />;
} else if (column.dataIndex === 'type') {
return (
<div class="flex items-center">
<img
src={record.type === EnumManuscriptType.Image ? icon2 : icon3}
width="16"
height="16"
class="mr-4px"
/>
<span
class={`cts !text-14px !lh-22px ${
record.type === EnumManuscriptType.Image ? '!color-#25C883' : '!color-#6D4CFE'
}`}
>
{record.type === EnumManuscriptType.Image ? '图文' : '视频'}
</span>
</div>
);
} else if (column.dataIndex === 'last_modified_at') {
return (
<span class="cts num">
{exactFormatTime(record.last_modified_at, 'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss')}
</span>
);
} else {
return formatTableField(column, record, true);
}
}}
/>
))}
</Table>
{pageInfo.value.total > 0 && (
<div class="flex justify-end mt-16px">
<Pagination
total={pageInfo.value.total}
size="mini"
show-total
show-jumper
show-page-size
size="small"
showTotal={(total, range) => `${total}`}
showQuickJumper
showSizeChanger
current={pageInfo.value.page}
page-size={pageInfo.value.page_size}
pageSize={pageInfo.value.page_size}
onChange={onPageChange}
onPageSizeChange={onPageSizeChange}
/>
</div>
)}

View File

@ -1,5 +1,5 @@
<script lang="jsx">
import { Modal, Form, FormItem, Input, Button, Message as AMessage } from '@arco-design/web-vue';
import { Button, Modal, Form, FormItem, Input, Tooltip, message } from 'ant-design-vue';
import CommonSelect from '@/components/common-select';
import { useClipboard } from '@vueuse/core';
@ -65,24 +65,22 @@ export default {
};
const onGenerateLink = () => {
formRef.value.validate().then(async (errors) => {
if (!errors) {
try {
loading.value = true;
const { code, data } = await postShareLinksGenerate(formData.value);
if (code === 200) {
onClose();
formRef.value.validate().then(async () => {
try {
loading.value = true;
const { code, data } = await postShareLinksGenerate(formData.value);
if (code === 200) {
onClose();
const url = router.resolve({
path: `/explore/list/${data.code}`,
}).href;
copy(generateFullUrl(url));
AMessage.success('链接已复制!');
emit('close');
}
} finally {
loading.value = false;
const url = router.resolve({
path: `/explore/list/${data.code}`,
}).href;
copy(generateFullUrl(url));
message.success('链接已复制!');
emit('close');
}
} finally {
loading.value = false;
}
});
};
@ -96,27 +94,33 @@ export default {
});
return () => (
<Modal
v-model:visible={visible.value}
v-model:open={visible.value}
title="分享内容稿件"
width="480px"
onClose={onClose}
unmount-on-close
onCancel={onClose}
destroyOnClose
centered
auto-label-width
v-slots={{
footer: () => (
<>
<Button size="medium" onClick={onClose}>
取消
</Button>
<Button type="primary" class="ml-16px" size="medium" onClick={onGenerateLink} disabled={loading.value}>
<Button onClick={onClose}>取消</Button>
<Button type="primary" class="ml-16px" onClick={onGenerateLink} disabled={loading.value}>
{loading.value ? '生成中...' : '生成链接'}
</Button>
</>
),
}}
>
<Form ref={formRef} rules={rules} model={formData.value} auto-label-width>
<FormItem label="有效期" prop="days" row-class="!items-center">
<Form
ref={formRef}
rules={rules}
model={formData.value}
labelAlign="right"
labelCol={{ span: 5 }}
wrapperCol={{ span: 19 }}
>
<FormItem label="有效期" name="days" row-class="!items-center">
<CommonSelect
v-model={formData.value.days}
options={OPTIONS}
@ -128,21 +132,20 @@ export default {
/>
</FormItem>
<FormItem
label="分享对象"
prop="receiver"
name="receiver"
row-class="!items-center"
v-slots={{
label: () => (
<div class="flex items-center">
<span>分享对象</span>
<a-tooltip content="可填写客户名称、昵称等,非必填" position="top">
<Tooltip title="可填写客户名称、昵称等,非必填" placement="top">
<icon-question-circle class="tooltip-icon color-#737478 ml-4px" size="14" />
</a-tooltip>
</Tooltip>
</div>
),
}}
>
<Input v-model={formData.value.receiver} class="!w-240px" size="large" placeholder="请输入分享对象" />
<Input v-model:value={formData.value.receiver} class="!w-240px" size="large" placeholder="请输入分享对象" />
</FormItem>
</Form>
</Modal>

View File

@ -18,49 +18,36 @@
font-size: 14px;
}
}
.arco-modal-body {
.ant-modal-body {
height: 464px;
display: flex;
flex-direction: column;
overflow: hidden;
.arco-scrollbar-track {
.ant-scrollbar-track {
display: none !important;
}
.arco-table {
.arco-table-container {
.arco-table-element {
thead {
.arco-table-tr {
.arco-table-th {
.arco-table-cell {
padding: 10px 16px !important;
}
}
}
}
tbody {
.arco-table-tr {
.arco-table-td {
.arco-table-cell {
padding: 6px 16px;
.arco-table-cell-content,
.arco-table-td-content {
font-size: 12px;
line-height: 20px;
}
}
}
}
.ant-table {
.ant-table-thead {
.ant-table-cell {
padding: 10px 16px !important;
}
}
.ant-table-body {
.ant-table-cell {
padding: 6px 16px !important;
.ant-table-cell-content {
font-size: 12px;
line-height: 20px;
}
}
}
}
.arco-pagination {
.arco-pagination-total,
.arco-pagination-jumper-prepend {
.ant-pagination {
.ant-pagination-total-text,
.ant-pagination-options-quick-jumper {
font-size: 14px;
}
.arco-pagination-jumper-prepend {
.ant-pagination-options-quick-jumper {
font-family: $font-family-regular;
}
}

View File

@ -1,16 +1,5 @@
<script lang="jsx">
import {
Modal,
Form,
FormItem,
Input,
RadioGroup,
Radio,
Upload,
Button,
Message as AMessage,
Textarea,
} from '@arco-design/web-vue';
import { Modal, Button, Form, FormItem, RadioGroup, Radio, Input, message, Upload } from 'ant-design-vue';
import { useClipboard } from '@vueuse/core';
import { getWriterLinksGenerate, getTemplateUrl, postWorksByLink, postWorksByFile } from '@/api/all/generationWorkshop';
import { generateFullUrl } from '@/utils/tools';
@ -18,6 +7,9 @@ import { slsWithCatch } from '@/utils/stroage.ts';
import TextOverTips from '@/components/text-over-tips';
import icon1 from '@/assets/img/media-account/icon-feedback-fail.png';
import icon2 from '@/assets/img/media-account/icon-download.png';
const { TextArea } = Input;
// 状态枚举
const TASK_STATUS = {
@ -41,7 +33,7 @@ const INITIAL_FORM = {
export default {
setup(props, { emit, expose }) {
const update = inject('update');
// const update = inject('update');
const router = useRouter();
// 响应式状态
@ -119,15 +111,13 @@ export default {
handleHandwriteSubmit();
return;
}
formRef.value?.validate(async (errors) => {
if (!errors) {
taskStatus.value = TASK_STATUS.LOADING;
const { link } = form.value;
const { code, data } = await postWorksByLink({ link });
if (code === 200) {
taskStatus.value = TASK_STATUS.SUCCESS;
works.value = data ? [data] : [];
}
formRef.value?.validate().then(async () => {
taskStatus.value = TASK_STATUS.LOADING;
const { link } = form.value;
const { code, data } = await postWorksByLink({ link });
if (code === 200) {
taskStatus.value = TASK_STATUS.SUCCESS;
works.value = data ? [data] : [];
}
});
}, 300);
@ -140,27 +130,25 @@ export default {
// 手写提交处理
const handleHandwriteSubmit = () => {
if (!form.value.writerLink) {
AMessage.warning('请输入上传链接!');
message.warning('请输入上传链接!');
return;
}
copy(form.value.writerLink);
AMessage.success('复制成功!');
message.success('复制成功!');
onClose();
};
// 取消上传
const onCancelUpload = () => {
taskStatus.value = TASK_STATUS.DEFAULT;
AMessage.info('已取消上传');
message.info('已取消上传');
};
// 文件上传处理
const handleUpload = async (option) => {
taskStatus.value = TASK_STATUS.LOADING;
const {
fileItem: { file },
} = option;
const { file } = option;
const formData = new FormData();
formData.append('file', file);
@ -222,9 +210,9 @@ export default {
// 渲染链接上传表单
const renderLinkForm = () => (
<FormItem label="链接地址" field="link" required>
<Textarea
v-model={form.value.link}
<FormItem label="链接地址" name="link" required>
<TextArea
v-model:value={form.value.link}
size="large"
placeholder="请输入飞书链接地址"
autoSize={{ minRows: 5, maxRows: 8 }}
@ -234,8 +222,8 @@ export default {
// 渲染手写上传表单
const renderHandwriteForm = () => (
<FormItem label="上传链接" field="writerLink">
<Input v-model={form.value.writerLink} placeholder="请输入上传链接" disabled size="large" />
<FormItem label="上传链接" name="writerLink">
<Input v-model:value={form.value.writerLink} placeholder="请输入上传链接" disabled size="large" />
</FormItem>
);
@ -245,24 +233,20 @@ export default {
<div class="flex flex-col w-full">
<Upload
action="/"
draggable
multiple
customRequest={handleUpload}
class="w-full"
accept=".xlsx,.xls,.docx,.doc,.mp4,.mov,.avi,.flv,.wmv,.m4v"
show-file-list={false}
showUploadList={false}
>
{{
'upload-button': () => (
<div class="upload-box">
<icon-plus size="14" class="mb-16px" />
<span class="text mb-4px">点击或拖拽文件到此处上传</span>
<span class="tip">支持文档文本+, 视频批量上传</span>
</div>
),
}}
<div class="upload-box">
<icon-plus size="14" class="mb-16px" />
<span class="text mb-4px">点击或拖拽文件到此处上传</span>
<span class="tip">支持文档文本+, 视频批量上传</span>
</div>
</Upload>
<div class="flex items-center cursor-pointer mt-8px" onClick={handleDownloadTemplate}>
<icon-download size="14" class="mr-4px !color-#6D4CFE" />
<img src={icon2} width="16" height="16" class="mr-4px" />
<span class="cts color-#6D4CFE">下载示例文档</span>
</div>
</div>
@ -332,36 +316,30 @@ export default {
const renderFooterButtons = () => {
const buttonMap = {
[TASK_STATUS.LOADING]: () => (
<Button type="primary" size="medium" onClick={onCancelUpload}>
<Button type="primary" onClick={onCancelUpload}>
取消上传
</Button>
),
[TASK_STATUS.DEFAULT]: () => (
<>
<Button size="medium" onClick={onClose}>
取消
</Button>
<Button type="primary" size="medium" onClick={onSubmit}>
<Button onClick={onClose}>取消</Button>
<Button type="primary" onClick={onSubmit}>
{isHandwrite.value ? '复制链接' : '确认'}
</Button>
</>
),
[TASK_STATUS.FAILED]: () => (
<>
<Button size="medium" onClick={onClose}>
取消
</Button>
<Button type="primary" size="medium" onClick={onClose}>
<Button onClick={onClose}>取消</Button>
<Button type="primary" onClick={onClose}>
重新上传
</Button>
</>
),
[TASK_STATUS.SUCCESS]: () => (
<>
<Button size="medium" onClick={onClose}>
取消
</Button>
<Button type="primary" size="medium" onClick={goUpload}>
<Button onClick={onClose}>取消</Button>
<Button type="primary" onClick={goUpload}>
确认
</Button>
</>
@ -375,17 +353,16 @@ export default {
return () => (
<Modal
v-model:visible={visible.value}
centered
v-model:open={visible.value}
title={getTitle()}
modal-class="upload-manuscript-modal"
wrapClassName="upload-manuscript-modal"
width="500px"
mask-closable={false}
unmount-on-close
onClose={onClose}
footer={!(isDefault.value && isLocal.value)}
v-slots={{
footer: () => renderFooterButtons(),
}}
maskClosable={false}
destroyOnClose
centered
onCancel={onClose}
footer={isDefault.value && isLocal.value ? null : renderFooterButtons()}
>
<Form
ref={formRef}
@ -398,7 +375,7 @@ export default {
>
{isDefault.value && (
<FormItem label="上传方式">
<RadioGroup v-model={uploadType.value} onChange={onUploadTypeChange}>
<RadioGroup v-model:value={uploadType.value} onChange={onUploadTypeChange}>
<Radio value={UPLOAD_TYPE.LINK}>链接上传</Radio>
<Radio value={UPLOAD_TYPE.LOCAL}>本地上传</Radio>
<Radio value={UPLOAD_TYPE.HANDWRITE}>写手上传</Radio>

View File

@ -28,4 +28,8 @@
border: 1px dashed var(--Border-1, #d7d7d9);
background: var(--BG-200, #f2f3f5);
}
.ant-upload {
cursor: pointer;
width: 100%;
}
}

View File

@ -1,5 +1,5 @@
<script lang="jsx">
import { Button, Message as AMessage, Spin } from '@arco-design/web-vue';
import { Button, Spin, message } from 'ant-design-vue';
import { useRouter, useRoute } from 'vue-router';
import { AuditStatus } from '@/views/material-center/components/finished-products/constants';
@ -119,12 +119,12 @@ export default {
return (
<>
<Button size="medium" type="outline" class="mr-12px" onClick={onBack}>
<Button type="primary" ghost class="mr-12px" onClick={onBack}>
退出
</Button>
<Button
size="medium"
type="outline"
type="primary"
ghost
class="mr-12px"
onClick={() =>
router.push({
@ -138,7 +138,7 @@ export default {
编辑
</Button>
{audit_status !== AuditStatus.Passed && (
<Button type="primary" size="medium" onClick={_fn}>
<Button type="primary" onClick={_fn}>
去审核
</Button>
)}
@ -157,7 +157,7 @@ export default {
});
return () => (
<Spin loading={loading.value} class="manuscript-detail-wrap" size={50}>
<Spin spinning={loading.value} wrapperClassName="manuscript-detail-wrap" size="large">
<div class="h-full w-full flex flex-col">
<div class="flex items-center mb-8px">
<span class="cts color-#4E5969 cursor-pointer" onClick={onBack}>

View File

@ -1,18 +1,19 @@
<template>
<a-modal v-model:visible="visible" title="退出编辑" width="480px" @close="onClose">
<Modal v-model:open="visible" title="退出编辑" centered width="480px" @cancel="onClose">
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>内容已修改尚未保存若退出编辑本次修改将不保存</span>
</div>
<template #footer>
<a-button size="medium" @click="onClose">继续编辑</a-button>
<a-button type="primary" class="ml-8px" size="medium" @click="onConfirm">确认退出</a-button>
<Button size="medium" @click="onClose">继续编辑</Button>
<Button type="primary" class="ml-8px" size="medium" @click="onConfirm">确认退出</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Button, Modal } from 'ant-design-vue';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
const router = useRouter();

View File

@ -1,5 +1,5 @@
<script lang="jsx">
import { Button, Message as AMessage } from '@arco-design/web-vue';
import { Button, message } from 'ant-design-vue';
import EditForm, { ENUM_UPLOAD_STATUS, INITIAL_VIDEO_INFO } from '../components/edit-form';
import CancelEditModal from './cancel-edit-modal.vue';
@ -43,14 +43,14 @@ export default {
const onSave = async (check = false) => {
formRef.value?.validate().then(async () => {
if (dataSource.value.videoInfo.uploadStatus === ENUM_UPLOAD_STATUS.UPLOADING) {
AMessage.warning('有视频正在上传中,请等待上传完成后再提交');
message.warning('有视频正在上传中,请等待上传完成后再提交');
return;
}
const filteredWorks = omit(dataSource.value, 'videoInfo');
const { code, data } = await putWorksUpdate({ id: workId.value, ...filteredWorks });
if (code === 200) {
AMessage.success('保存成功');
message.success('保存成功');
isSaved.value = true;
if (check) {

View File

@ -3,18 +3,17 @@
<div class="filter-row">
<div class="filter-row-item">
<span class="label">内容稿件标题</span>
<a-input
v-model="query.title"
<Input
v-model:value="query.title"
class="!w-240px"
placeholder="请输入内容稿件标题"
size="medium"
allow-clear
allowClear
@change="handleSearch"
>
<template #prefix>
<icon-search />
</template>
</a-input>
</Input>
</div>
<!-- <div class="filter-row-item">
<span class="label">所属项目</span>
@ -28,20 +27,17 @@
</div> -->
<div class="filter-row-item">
<span class="label">序号</span>
<a-space size="medium">
<a-input
v-model="query.uid"
class="!w-160px"
placeholder="请输入序号"
size="medium"
allow-clear
@change="handleSearch"
>
<template #prefix>
<icon-search />
</template>
</a-input>
</a-space>
<Input
v-model:value="query.uid"
class="!w-160px"
placeholder="请输入序号"
allowClear
@change="handleSearch"
>
<template #prefix>
<icon-search />
</template>
</Input>
</div>
<div class="filter-row-item">
<span class="label">审核状态</span>
@ -56,8 +52,8 @@
</div>
<div class="filter-row-item">
<span class="label">上传时间</span>
<a-range-picker
v-model="created_at"
<DatePicker.RangePicker
v-model:value="created_at"
size="medium"
allow-clear
format="YYYY-MM-DD"
@ -66,24 +62,25 @@
/>
</div>
<div class="filter-row-item">
<a-button type="outline" class="mr-12px" size="medium" @click="handleSearch">
<Button type="primary" ghost class="mr-12px" size="medium" @click="handleSearch">
<template #icon>
<icon-search />
<icon-search class="mr-8px" />
</template>
<template #default>搜索</template>
</a-button>
<a-button size="medium" @click="handleReset">
</Button>
<Button size="medium" @click="handleReset">
<template #icon>
<icon-refresh />
<icon-refresh class="mr-8px" />
</template>
<template #default>重置</template>
</a-button>
</Button>
</div>
</div>
</div>
</template>
<script setup>
import { Button, Input, DatePicker } from 'ant-design-vue';
import { defineEmits, defineProps } from 'vue';
import { CHECK_STATUS } from '@/views/material-center/components/finished-products/manuscript/list/constants';
import CommonSelect from '@/components/common-select';
@ -96,7 +93,7 @@ const props = defineProps({
},
});
const emits = defineEmits('search', 'reset', 'update:query');
const emits = defineEmits(['search', 'reset', 'update:query']);
const created_at = ref([]);
// const projects = ref([]);

View File

@ -67,7 +67,7 @@ export const TABLE_COLUMNS = [
{
title: '操作',
dataIndex: 'operation',
width: 180,
width: 200,
fixed: 'right',
},
];

View File

@ -1,25 +1,19 @@
<template>
<a-modal
v-model:visible="visible"
title="删除稿件"
width="480px"
@close="onClose"
>
<Modal v-model:open="visible" title="删除稿件" centered width="480px" @cancel="onClose">
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认删除 {{ projectName }} 这个稿件吗</span>
<span>确认删除 {{ projectName }} 这个稿件吗</span>
</div>
<template #footer>
<a-button size="large" @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px" status="danger" size="large" @click="onDelete"
>确认删除</a-button
>
<Button size="large" @click="onClose">取消</Button>
<Button type="primary" class="ml-16px" danger size="large" @click="onDelete">确认删除</Button>
</template>
</a-modal>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Button, Modal, message } from 'ant-design-vue';
import { deleteWork } from '@/api/all/generationWorkshop';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
@ -48,8 +42,8 @@ const open = (record) => {
async function onDelete() {
const { code } = await deleteWork(projectId.value);
if (code === 200) {
AMessage.success('删除成功');
update()
message.success('删除成功');
update();
onClose();
}
}

View File

@ -1,123 +1,114 @@
<template>
<a-table
<Table
ref="tableRef"
:data="dataSource"
row-key="id"
column-resizable
:dataSource="dataSource"
rowKey="id"
:pagination="false"
:scroll="{ x: '100%' }"
class="manuscript-table w-100% flex-1"
bordered
@sorter-change="handleSorterChange"
:showSorterTooltip="false"
@change="handleTableChange"
>
<template #empty>
<template #emptyText>
<NoData text="暂无稿件" />
</template>
<template #columns>
<a-table-column
v-for="column in TABLE_COLUMNS"
:key="column.dataIndex"
:data-index="column.dataIndex"
:fixed="column.fixed"
:width="column.width"
:min-width="column.minWidth"
:sortable="column.sortable"
:align="column.align"
ellipsis
tooltip
>
<template #title>
<div class="flex items-center">
<span class="cts mr-4px">{{ column.title }}</span>
<a-tooltip v-if="column.tooltip" :content="column.tooltip" position="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</a-tooltip>
</div>
</template>
<Column
v-for="column in TABLE_COLUMNS"
:key="column.dataIndex"
:dataIndex="column.dataIndex"
:fixed="column.fixed"
:width="column.width"
:minWidth="column.minWidth"
:sorter="column.sortable"
:align="column.align"
:ellipsis="true"
>
<template #title>
<span class="cts mr-4px">{{ column.title }}</span>
<Tooltip v-if="column.tooltip" :title="column.tooltip" placement="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</Tooltip>
</template>
<template v-if="column.dataIndex === 'create_at'" #cell="{ record }">
{{ exactFormatTime(record.create_at) }}
</template>
<template v-else-if="column.dataIndex === 'customer_opinion'" #cell="{ record }">
<p
class="h-28px px-8px flex items-center rounded-2px w-fit"
:style="{ background: getCustomerOpinionInfo(record.customer_opinion)?.bg }"
>
<span class="cts" :class="getCustomerOpinionInfo(record.customer_opinion)?.color">{{
getCustomerOpinionInfo(record.customer_opinion)?.label ?? '-'
}}</span>
</p>
</template>
<template v-else-if="column.dataIndex === 'title'" #cell="{ record }">
<TextOverTips :context="record.title" :line="3" class="title" @click="onDetail(record)" />
</template>
<template v-else-if="column.dataIndex === 'audit_status'" #cell="{ record }">
<div
class="flex items-center w-fit h-28px px-8px rounded-2px"
:style="{ backgroundColor: getStatusInfo(record.audit_status).backgroundColor }"
>
<span class="cts s1" :style="{ color: getStatusInfo(record.audit_status).color }">{{
getStatusInfo(record.audit_status).name
}}</span>
</div>
</template>
<template v-else-if="column.dataIndex === 'type'" #cell="{ record }">
<div class="flex items-center">
<img
:src="record.type === EnumManuscriptType.Image ? icon2 : icon3"
width="16"
height="16"
class="mr-4px"
/>
<span class="cts" :class="record.type === EnumManuscriptType.Image ? '!color-#25C883' : '!color-#6D4CFE'">{{
record.type === EnumManuscriptType.Image ? '图文' : '视频'
}}</span>
</div>
</template>
<template v-else-if="['uploader', 'last_modifier'].includes(column.dataIndex)" #cell="{ record }">
{{ record[column.dataIndex].name || record[column.dataIndex].mobile }}
</template>
<template v-else-if="['created_at', 'last_modified_at'].includes(column.dataIndex)" #cell="{ record }">
{{ exactFormatTime(record[column.dataIndex]) }}
</template>
<template v-else-if="column.dataIndex === 'cover'" #cell="{ record }">
<HoverImagePreview :src="record.cover">
<a-image :width="64" :height="64" :src="record.cover" class="!rounded-6px" fit="cover">
<template #error>
<img :src="icon4" class="w-full h-full" />
</template>
</a-image>
</HoverImagePreview>
</template>
<template v-else-if="column.dataIndex === 'operation'" #cell="{ record }">
<div class="flex items-center">
<img class="mr-8px cursor-pointer" :src="icon1" width="14" height="14" @click="onDelete(record)" />
<a-button type="outline" size="mini" class="mr-8px" @click="onEdit(record)">编辑</a-button>
<a-button type="outline" size="mini" @click="onDetail(record)">详情</a-button>
</div>
</template>
<template v-else #cell="{ record }">
{{ formatTableField(column, record, true) }}
</template>
</a-table-column>
</template>
</a-table>
<template v-if="column.dataIndex === 'create_at'" #customRender="{ record }">
{{ exactFormatTime(record.create_at) }}
</template>
<template v-else-if="column.dataIndex === 'customer_opinion'" #customRender="{ record }">
<p
class="h-28px px-8px flex items-center rounded-2px w-fit"
:style="{ background: getCustomerOpinionInfo(record.customer_opinion)?.bg }"
>
<span class="cts" :class="getCustomerOpinionInfo(record.customer_opinion)?.color">{{
getCustomerOpinionInfo(record.customer_opinion)?.label ?? '-'
}}</span>
</p>
</template>
<template v-else-if="column.dataIndex === 'title'" #customRender="{ record }">
<TextOverTips :context="record.title" :line="3" class="title" @click="onDetail(record)" />
</template>
<template v-else-if="column.dataIndex === 'audit_status'" #customRender="{ record }">
<div
class="flex items-center w-fit h-28px px-8px rounded-2px"
:style="{ backgroundColor: getStatusInfo(record.audit_status).backgroundColor }"
>
<span class="cts s1" :style="{ color: getStatusInfo(record.audit_status).color }">{{
getStatusInfo(record.audit_status).name
}}</span>
</div>
</template>
<template v-else-if="column.dataIndex === 'type'" #customRender="{ record }">
<div class="flex items-center">
<img :src="record.type === EnumManuscriptType.Image ? icon2 : icon3" width="16" height="16" class="mr-4px" />
<span class="cts" :class="record.type === EnumManuscriptType.Image ? '!color-#25C883' : '!color-#6D4CFE'">{{
record.type === EnumManuscriptType.Image ? '图文' : '视频'
}}</span>
</div>
</template>
<template v-else-if="['uploader', 'last_modifier'].includes(column.dataIndex)" #customRender="{ record }">
{{ record[column.dataIndex].name || record[column.dataIndex].mobile }}
</template>
<template v-else-if="['created_at', 'last_modified_at'].includes(column.dataIndex)" #customRender="{ record }">
{{ exactFormatTime(record[column.dataIndex]) }}
</template>
<template v-else-if="column.dataIndex === 'cover'" #customRender="{ record }">
<HoverImagePreview :src="record.cover">
<ImgLazyLoad :width="64" :height="64" :src="record.cover" class="!rounded-6px" />
</HoverImagePreview>
</template>
<template v-else-if="column.dataIndex === 'operation'" #customRender="{ record }">
<div class="flex items-center">
<img class="mr-8px cursor-pointer" :src="icon1" width="14" height="14" @click="onDelete(record)" />
<Button type="primary" ghost size="small" class="mr-8px" @click="onEdit(record)">编辑</Button>
<Button type="primary" ghost size="small" @click="onDetail(record)">详情</Button>
</div>
</template>
<template v-else #customRender="{ record }">
{{ formatTableField(column, record, true) }}
</template>
</Column>
</Table>
</template>
<script setup>
import { ref } from 'vue';
import { Button, Tooltip, Table, Image } from 'ant-design-vue';
const { Column } = Table;
import { formatTableField, exactFormatTime } from '@/utils/tools';
import { TABLE_COLUMNS } from './constants';
import { CHECK_STATUS, EnumManuscriptType } from '@/views/material-center/components/finished-products/manuscript/list/constants';
import {
CHECK_STATUS,
EnumManuscriptType,
} from '@/views/material-center/components/finished-products/manuscript/list/constants';
import { CUSTOMER_OPINION } from '@/views/material-center/components/finished-products/manuscript/check-list/constants';
import TextOverTips from '@/components/text-over-tips';
import HoverImagePreview from '@/components/hover-image-preview';
import ImgLazyLoad from "@/components/img-lazy-load";
import icon1 from '@/assets/img/media-account/icon-delete.png';
import icon2 from '@/assets/img/creative-generation-workshop/icon-photo.png';
import icon3 from '@/assets/img/creative-generation-workshop/icon-video.png';
import icon4 from '@/assets/img/error-img.png';
const emits = defineEmits(['edit', 'sorterChange', 'delete']);
const router = useRouter();
@ -131,8 +122,10 @@ const props = defineProps({
const tableRef = ref(null);
const handleSorterChange = (column, order) => {
emits('sorterChange', column, order === 'ascend' ? 'asc' : 'desc');
const handleTableChange = (pagination, filters, sorter) => {
if (sorter && sorter.field) {
emits('sorterChange', sorter.field, sorter.order === 'ascend' ? 'asc' : 'desc');
}
};
const onDelete = (item) => {
emits('delete', item);

Some files were not shown because too many files have changed in this diff Show More