Merge remote-tracking branch 'origin/main' into feature/v1.3_营销资产中台
96
.drone.yml
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: build-deploy
|
||||||
|
|
||||||
|
clone:
|
||||||
|
depth: 1 # ✅ 只拉取最近一次 commit,显著加快 clone 速度
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
- custom
|
||||||
|
branch:
|
||||||
|
- master
|
||||||
|
- test
|
||||||
|
- feature/ldb_build
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: install & build
|
||||||
|
image: node:23.9.0
|
||||||
|
volumes:
|
||||||
|
- name: build-output
|
||||||
|
path: /runner/builds
|
||||||
|
commands:
|
||||||
|
- corepack enable
|
||||||
|
- corepack prepare pnpm@8.15.5 --activate
|
||||||
|
- pnpm install
|
||||||
|
- mkdir -p /runner/builds/${DRONE_REPO_NAME}/${DRONE_BRANCH}
|
||||||
|
- |
|
||||||
|
case "${DRONE_BRANCH}" in
|
||||||
|
feature/ldb_build | test)
|
||||||
|
pnpm run build:test
|
||||||
|
;;
|
||||||
|
master)
|
||||||
|
pnpm run build:prod
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "❌ 未配置此分支的构建规则: ${DRONE_BRANCH}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
- rm -rf /runner/builds/${DRONE_REPO_NAME}/${DRONE_BRANCH}/*
|
||||||
|
- cp -r dist/* /runner/builds/${DRONE_REPO_NAME}/${DRONE_BRANCH}/
|
||||||
|
|
||||||
|
- name: deploy to spug
|
||||||
|
image: curlimages/curl
|
||||||
|
when:
|
||||||
|
status:
|
||||||
|
- success
|
||||||
|
branch:
|
||||||
|
- feature/ldb_build
|
||||||
|
- test
|
||||||
|
- master
|
||||||
|
environment:
|
||||||
|
SPUG_DEPLOY_URL:
|
||||||
|
from_secret: spug_deploy_lingji_work_fe_url
|
||||||
|
SPUG_DEPLOY_TOKEN:
|
||||||
|
from_secret: spug_deploy_token
|
||||||
|
commands:
|
||||||
|
- |
|
||||||
|
echo "🚀 部署到 Spug: 分支 ${DRONE_BRANCH}"
|
||||||
|
curl -X POST "$SPUG_DEPLOY_URL?name=${DRONE_BRANCH}&token=$SPUG_DEPLOY_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"ref": "refs/heads/'"${DRONE_BRANCH}"'",
|
||||||
|
"before": "'"${DRONE_COMMIT_BEFORE}"'",
|
||||||
|
"after": "'"${DRONE_COMMIT_SHA}"'",
|
||||||
|
"commits": [
|
||||||
|
{
|
||||||
|
"message": "发布'"${DRONE_BRANCH}:${DRONE_COMMIT_SHA}"'"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
|
||||||
|
- name: notify feishu on failure
|
||||||
|
image: curlimages/curl
|
||||||
|
when:
|
||||||
|
status:
|
||||||
|
- failure
|
||||||
|
environment:
|
||||||
|
FEISHU_WEBHOOK:
|
||||||
|
from_secret: feishu_webhook_url
|
||||||
|
commands:
|
||||||
|
- |
|
||||||
|
curl -X POST "$FEISHU_WEBHOOK" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"msg_type": "text",
|
||||||
|
"content": {
|
||||||
|
"text": "❌ Drone-CI 执行失败 ❗️\n项目: '${DRONE_REPO_NAME}'\n分支: '${DRONE_BRANCH}'\n提交: '${DRONE_COMMIT_SHA:0:8}'"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: build-output
|
||||||
|
host:
|
||||||
|
path: /www/dk_project/dk_compose/spug/data/repos/build/drone-runner/builds
|
||||||
@ -28,6 +28,14 @@ module.exports = {
|
|||||||
tsx: '@typescript-eslint/parser',
|
tsx: '@typescript-eslint/parser',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
babelOptions: {
|
||||||
|
presets: [
|
||||||
|
'@babel/preset-env'
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
'@vue/babel-plugin-jsx'
|
||||||
|
]
|
||||||
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/prefer-optional-chain': 'off',
|
'@typescript-eslint/prefer-optional-chain': 'off',
|
||||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
|
|||||||
@ -1,14 +1,9 @@
|
|||||||
/*
|
|
||||||
* @Author: RenXiaoDong
|
|
||||||
* @Date: 2025-06-24 16:29:10
|
|
||||||
*/
|
|
||||||
/**
|
/**
|
||||||
* 自动引入API
|
* 自动引入API
|
||||||
* */
|
* */
|
||||||
import AutoImport from 'unplugin-auto-import/vite';
|
import AutoImport from 'unplugin-auto-import/vite';
|
||||||
|
|
||||||
import { ArcoResolver } from 'unplugin-vue-components/resolvers';
|
import { ArcoResolver } from 'unplugin-vue-components/resolvers';
|
||||||
import IconsResolver from 'unplugin-icons/resolver';
|
|
||||||
|
|
||||||
import { layoutsResolver } from '../utils';
|
import { layoutsResolver } from '../utils';
|
||||||
|
|
||||||
@ -21,7 +16,7 @@ export function configAutoImport() {
|
|||||||
'@vueuse/core',
|
'@vueuse/core',
|
||||||
{
|
{
|
||||||
dayjs: [['default', 'dayjs']],
|
dayjs: [['default', 'dayjs']],
|
||||||
'lodash-es': ['cloneDeep', 'omit', 'pick', 'union', 'uniq', 'isNumber', 'uniqBy', 'isEmpty'],
|
'lodash-es': ['cloneDeep', 'omit', 'pick', 'union', 'uniq', 'isNumber', 'uniqBy', 'isEmpty', 'merge', 'debounce'],
|
||||||
'@/hooks': ['useModal'],
|
'@/hooks': ['useModal'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -31,9 +26,6 @@ export function configAutoImport() {
|
|||||||
enable: true,
|
enable: true,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
IconsResolver({
|
|
||||||
enabledCollections: [],
|
|
||||||
}),
|
|
||||||
layoutsResolver(),
|
layoutsResolver(),
|
||||||
],
|
],
|
||||||
eslintrc: {
|
eslintrc: {
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { kebabCase } from 'unplugin-vue-components';
|
|||||||
import Components from 'unplugin-vue-components/vite';
|
import Components from 'unplugin-vue-components/vite';
|
||||||
|
|
||||||
import { ArcoResolver } from 'unplugin-vue-components/resolvers';
|
import { ArcoResolver } from 'unplugin-vue-components/resolvers';
|
||||||
import IconsResolver from 'unplugin-icons/resolver';
|
|
||||||
|
|
||||||
import { getSep, getPath, setResolve, layoutsResolver } from '../utils';
|
import { getSep, getPath, setResolve, layoutsResolver } from '../utils';
|
||||||
|
|
||||||
@ -20,11 +19,6 @@ export function configComponents() {
|
|||||||
},
|
},
|
||||||
sideEffect: true,
|
sideEffect: true,
|
||||||
}),
|
}),
|
||||||
IconsResolver({
|
|
||||||
prefix: false,
|
|
||||||
customCollections: ['i'],
|
|
||||||
enabledCollections: [],
|
|
||||||
}),
|
|
||||||
layoutsResolver(),
|
layoutsResolver(),
|
||||||
{
|
{
|
||||||
type: 'component',
|
type: 'component',
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
/**
|
// /**
|
||||||
* 自动引入 svg 图标
|
// * 自动引入 svg 图标
|
||||||
* */
|
// * */
|
||||||
import Icons from 'unplugin-icons/vite';
|
import { resolve } from '../utils';
|
||||||
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
|
|
||||||
|
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
|
||||||
|
|
||||||
export function configIcons() {
|
export function configIcons() {
|
||||||
return Icons({
|
return createSvgIconsPlugin({
|
||||||
compiler: 'vue3',
|
iconDirs: [resolve( "src/assets/svg")],
|
||||||
customCollections: {
|
symbolId: "icon-[dir]-[name]",
|
||||||
i: FileSystemIconLoader('./src/assets', (svg) => svg.replace(/^<svg /, '<svg class="eo-i" ')),
|
})
|
||||||
},
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -69,6 +69,7 @@
|
|||||||
"vite": "^4.0.4",
|
"vite": "^4.0.4",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-progress": "^0.0.6",
|
"vite-plugin-progress": "^0.0.6",
|
||||||
|
"vite-plugin-svg-icons": "^2.0.1",
|
||||||
"vue-eslint-parser": "^9.1.0",
|
"vue-eslint-parser": "^9.1.0",
|
||||||
"vue-tsc": "^1.0.24"
|
"vue-tsc": "^1.0.24"
|
||||||
},
|
},
|
||||||
|
|||||||
12
src/App.vue
@ -5,14 +5,16 @@
|
|||||||
</a-config-provider>
|
</a-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { useUserStore } from '@/stores';
|
import { useUserStore } from '@/stores';
|
||||||
|
|
||||||
import { getUserEnterpriseInfo } from '@/utils/user';
|
import { getUserEnterpriseInfo } from '@/utils/user';
|
||||||
|
import { useSidebarStore } from '@/stores/modules/side-bar';
|
||||||
|
|
||||||
import zhCN from '@arco-design/web-vue/es/locale/lang/zh-cn';
|
import zhCN from '@arco-design/web-vue/es/locale/lang/zh-cn';
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const sidebarStore = useSidebarStore();
|
||||||
|
|
||||||
const redTheme = {
|
const redTheme = {
|
||||||
token: {
|
token: {
|
||||||
@ -20,7 +22,6 @@ const redTheme = {
|
|||||||
colorLink: '#f5222d', // 链接色
|
colorLink: '#f5222d', // 链接色
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// 初始化企业信息
|
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
const { isLogin, getUserInfo } = userStore;
|
const { isLogin, getUserInfo } = userStore;
|
||||||
@ -28,6 +29,10 @@ const init = async () => {
|
|||||||
if (isLogin) {
|
if (isLogin) {
|
||||||
await getUserInfo(); // 初始化用户信息
|
await getUserInfo(); // 初始化用户信息
|
||||||
await getUserEnterpriseInfo();
|
await getUserEnterpriseInfo();
|
||||||
|
|
||||||
|
sidebarStore.startUnreadInfoPolling();
|
||||||
|
} else {
|
||||||
|
sidebarStore.stopUnreadInfoPolling();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -40,6 +45,9 @@ onMounted(() => {
|
|||||||
console.error(`发现catch报错:${event.reason}`);
|
console.error(`发现catch报错:${event.reason}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
onUnmounted(() => {
|
||||||
|
sideBarStore.stopUnreadInfoPolling();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@ -13,3 +13,52 @@ export const getCustomColumns = (params = {}) => {
|
|||||||
export const updateCustomColumns = (params = {}) => {
|
export const updateCustomColumns = (params = {}) => {
|
||||||
return Http.put('/v1/custom-columns', params);
|
return Http.put('/v1/custom-columns', params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getUserList = (params = {}) => {
|
||||||
|
return Http.get('/v1/users/list', params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 任务中心-分页
|
||||||
|
export const getTask = (params = {}) => {
|
||||||
|
return Http.get('/v1/tasks', params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 任务中心-批量删除
|
||||||
|
export const deleteBatchTasks = (params = {}) => {
|
||||||
|
return Http.delete('/v1/tasks', { data: params });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 任务中心-删除
|
||||||
|
export const deleteTask = (id: string) => {
|
||||||
|
return Http.delete(`/v1/tasks/${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 任务中心-查询任务状态
|
||||||
|
export const getTaskStatus = (id: string) => {
|
||||||
|
return Http.get(`/v1/tasks/${id}/status`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 任务中心-获取未读任务
|
||||||
|
export const getTaskUnread = () => {
|
||||||
|
return Http.get(`/v1/tasks/unread`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 任务中心-已读
|
||||||
|
export const patchTaskRead = (params = {}) => {
|
||||||
|
return Http.patch('/v1/tasks/read', params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 任务中心-重做任务
|
||||||
|
export const postRedoTask = (id: string) => {
|
||||||
|
return Http.post(`/v1/tasks/${id}/redo`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 任务中心-批量下载
|
||||||
|
export const postBatchDownload = (params = {}) => {
|
||||||
|
return Http.post(`/v1/tasks/batch-download`, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 任务中心-批量查询任务状态
|
||||||
|
export const batchQueryTaskStatus = (params = {}) => {
|
||||||
|
return Http.get(`/v1/tasks/batch-query-status`, params);
|
||||||
|
};
|
||||||
@ -24,6 +24,11 @@ export const getMediaAccounts = (params = {}) => {
|
|||||||
return Http.get('/v1/media-accounts', params);
|
return Http.get('/v1/media-accounts', params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 媒体账号-列表
|
||||||
|
export const getMediaAccountList = (params = {}) => {
|
||||||
|
return Http.get('/v1/media-accounts/list', params);
|
||||||
|
};
|
||||||
|
|
||||||
// 媒体账号-健康情况
|
// 媒体账号-健康情况
|
||||||
export const getMediaAccountsHealth = (params = {}) => {
|
export const getMediaAccountsHealth = (params = {}) => {
|
||||||
return Http.get('/v1/media-accounts/health', params);
|
return Http.get('/v1/media-accounts/health', params);
|
||||||
@ -332,4 +337,68 @@ export const postPlacementAccountsSync = (id: string) => {
|
|||||||
return Http.post(`/v1/placement-accounts/${id}/sync-data`);
|
return Http.post(`/v1/placement-accounts/${id}/sync-data`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 投放账号-子账号分页
|
||||||
|
export const postSubAccount = (params = {}) => {
|
||||||
|
return Http.post('/v1/placement-accounts/get-subaccount', params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 投放账号-添加子账号
|
||||||
|
export const postAddSubAccount = (params = {}) => {
|
||||||
|
return Http.post('/v1/placement-accounts/subaccount', params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 媒体账号-同步数据
|
||||||
|
export const postSyncMediaAccountData = (id: string) => {
|
||||||
|
return Http.post(`/v1/media-accounts/${id}/sync-data`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 媒体账号-批量同步数据
|
||||||
|
export const postBatchSyncMediaAccountData = (params: {}) => {
|
||||||
|
return Http.post(`/v1/media-accounts/batch-sync-data`, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 媒体账号-查询同步状态
|
||||||
|
export const getMediaAccountSyncStatus = (params = {}) => {
|
||||||
|
return Http.get('/v1/media-accounts/sync-status', params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 媒体账号-移除同步状态
|
||||||
|
export const deleteSyncStatus = (id: string) => {
|
||||||
|
return Http.delete(`/v1/media-accounts/${id}/sync-status`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 内容稿件-列表
|
||||||
|
export const getWorksList = (params = {}) => {
|
||||||
|
return Http.get('/v1/works/list', params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 项目管理-分页
|
||||||
|
export const getProjects = (params = {}) => {
|
||||||
|
return Http.get('/v1/projects', params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 项目管理-列表
|
||||||
|
export const getProjectList = () => {
|
||||||
|
return Http.get('/v1/projects/list');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 项目管理-删除
|
||||||
|
export const deleteProject = (id: string) => {
|
||||||
|
return Http.delete(`/v1/projects/${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 项目管理-添加
|
||||||
|
export const postAddProject = (params: {}) => {
|
||||||
|
return Http.post('/v1/projects', params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 项目管理-修改
|
||||||
|
export const putProject = (params = {}) => {
|
||||||
|
const { id, ...rest } = params as { id: string; [key: string]: any };
|
||||||
|
return Http.put(`/v1/projects/${id}`, rest);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 项目管理-详情
|
||||||
|
export const getProjectDetail = (id: string) => {
|
||||||
|
return Http.get(`/v1/projects/${id}`);
|
||||||
|
};
|
||||||
|
|||||||
BIN
src/assets/img/media-account/icon-schedule.png
Normal file
|
After Width: | Height: | Size: 701 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 571 B After Width: | Height: | Size: 571 B |
3
src/assets/svg/svg-projectManagement.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||||
|
<path d="M5.25065 1.3125C5.4638 1.31254 5.67398 1.36363 5.86328 1.46159L7.86328 2.49674C8.05258 2.5947 8.26278 2.64579 8.47591 2.64583L13.3822 2.64583C14.1185 2.64583 14.7155 3.24282 14.7155 3.97917L14.7155 13.3542C14.7155 14.0905 14.1185 14.6875 13.3822 14.6875L2.61784 14.6875C1.88146 14.6875 1.28451 14.0905 1.28451 13.3542L1.28451 2.64583C1.28455 1.90949 1.88149 1.3125 2.61784 1.3125L5.25065 1.3125ZM4.528 9.99023C4.14147 9.9903 3.82815 10.3036 3.82813 10.6901C3.82813 11.0767 4.14146 11.3899 4.528 11.39C4.91459 11.39 5.22787 11.0767 5.22787 10.6901C5.22784 10.3035 4.91458 9.99023 4.528 9.99023ZM7.153 10.0905C6.82179 10.0907 6.55342 10.3589 6.55339 10.6901C6.55339 11.0214 6.82177 11.2902 7.153 11.2904L11.5384 11.2904C11.8698 11.2904 12.1387 11.0215 12.1387 10.6901C12.1386 10.3588 11.8698 10.0905 11.5384 10.0905L7.153 10.0905ZM4.528 6.50846C4.14155 6.50853 3.82828 6.82191 3.82813 7.20833C3.82813 7.59489 4.14146 7.90878 4.528 7.90885C4.91459 7.90885 5.22787 7.59493 5.22787 7.20833C5.22771 6.82187 4.9145 6.50846 4.528 6.50846ZM7.153 6.60872C6.82187 6.60889 6.55355 6.87721 6.55339 7.20833C6.55339 7.5396 6.82177 7.80843 7.153 7.80859H11.5384C11.8698 7.80859 12.1387 7.5397 12.1387 7.20833C12.1385 6.8771 11.8697 6.60872 11.5384 6.60872H7.153Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 984 B After Width: | Height: | Size: 984 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
3
src/assets/svg/svg-taskCenter.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||||
|
<path d="M9.2041 2.32617C11.6897 2.32617 13.7595 4.10156 14.2178 6.45312C15.2902 7.15513 16 8.36571 16 9.74316C16 11.9142 14.2393 13.6738 12.0684 13.6738H3.93066C1.75983 13.6736 3.83345e-05 11.9141 0 9.74316C0 9.63068 0.00538693 9.51927 0.0146484 9.40918C0.00563418 9.29576 7.49779e-06 9.18117 0 9.06543C0 6.69938 1.91817 4.78127 4.28418 4.78125C4.46476 4.78126 4.64268 4.79275 4.81738 4.81445C5.70979 3.32391 7.34052 2.32633 9.2041 2.32617ZM9.2041 3.52539C7.77983 3.52555 6.53145 4.28786 5.84668 5.43164L5.44531 6.10156L4.66992 6.00586C4.53994 5.9897 4.41129 5.98048 4.28418 5.98047C2.6345 5.98048 1.28692 7.27669 1.2041 8.90625L1.2002 9.06543C1.2002 9.14467 1.20305 9.22752 1.20996 9.31445L1.21777 9.41211L1.20996 9.50977L1.2002 9.74316C1.20023 11.2514 2.42259 12.4744 3.93066 12.4746H12.0684C13.5297 12.4746 14.7226 11.3265 14.7959 9.88379L14.7998 9.74316C14.7998 8.78847 14.3094 7.94732 13.5605 7.45703L13.1377 7.17969L13.04 6.68262C12.6894 4.8834 11.104 3.52539 9.2041 3.52539ZM6.79688 6.74121C6.93478 6.61179 7.13694 6.57633 7.31055 6.65137C7.48407 6.72651 7.59654 6.89783 7.59668 7.08691V10.749C7.59642 11.0109 7.38396 11.2235 7.12207 11.2236C6.86013 11.2236 6.64772 11.0109 6.64746 10.749V8.18457L6.05859 8.73828C5.86754 8.91775 5.5662 8.90785 5.38672 8.7168C5.20757 8.52576 5.21736 8.2253 5.4082 8.0459L6.79688 6.74121ZM8.87793 6.6123C9.14003 6.61235 9.35254 6.82578 9.35254 7.08789V9.65137L9.94141 9.09863C10.1325 8.91917 10.4338 8.92809 10.6133 9.11914C10.7925 9.31017 10.7827 9.61061 10.5918 9.79004L9.20312 11.0947C9.0652 11.2242 8.86308 11.2596 8.68945 11.1846C8.51588 11.1094 8.40339 10.9382 8.40332 10.749V7.08789C8.40332 6.82582 8.61588 6.61241 8.87793 6.6123Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="m-0 p-0 mb-24px s2 ml-32px">退出登录后,你将无法收到该账号的通知</p>
|
<p class="m-0 p-0 mb-24px s2 ml-32px">退出登录后,你将无法收到该账号的通知</p>
|
||||||
<div class="flex items-center justify-end">
|
<div class="flex items-center justify-end">
|
||||||
<a-button class="cancel-btn" size="medium" @click="onClose">返回</a-button>
|
<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 type="primary" class="ml-16px danger-btn" status="danger" size="medium" @click="onLogout"
|
||||||
>退出登录</a-button
|
>退出登录</a-button
|
||||||
>
|
>
|
||||||
@ -83,15 +83,12 @@ defineExpose({ open });
|
|||||||
}
|
}
|
||||||
.cancel-btn {
|
.cancel-btn {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid var(--BG-500, #b1b2b5);
|
|
||||||
&:hover {
|
|
||||||
border: 1px solid var(--BG-500, #b1b2b5);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.danger-btn {
|
.danger-btn {
|
||||||
border-radius: 4px;
|
|
||||||
background: var(--Functional-Danger-6, #f64b31) !important;
|
background: var(--Functional-Danger-6, #f64b31) !important;
|
||||||
border: none !important;
|
&:hover {
|
||||||
|
background: var(--Functional-Danger-6, #f64b31) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
93
src/components/_base/navbar/components/navbar-menu/index.vue
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<div class="navbar-menu h-100%">
|
||||||
|
<a-menu mode="horizontal" :selected-keys="selectedKey">
|
||||||
|
<a-menu-item v-for="item in menuList" :key="String(item.id)">
|
||||||
|
<template v-if="item.children">
|
||||||
|
<a-dropdown :popup-max-height="false" class="layout-menu-item-dropdown">
|
||||||
|
<a-button type="text">
|
||||||
|
<span class="menu-item-text mr-2px"> {{ item.name }}</span>
|
||||||
|
<icon-caret-down size="16" class="arco-icon-down !mr-0" />
|
||||||
|
</a-button>
|
||||||
|
<template #content>
|
||||||
|
<a-doption
|
||||||
|
v-for="(child, ind) in item.children"
|
||||||
|
:key="ind"
|
||||||
|
@click="handleDropdownClick(child)"
|
||||||
|
:class="{ active: child.includeRouteNames.includes(route.name) }"
|
||||||
|
>
|
||||||
|
<span class="menu-item-text"> {{ child.name }}</span>
|
||||||
|
</a-doption>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-menu-item :key="String(item.id)" @click="handleDropdownClick(item)">
|
||||||
|
<span class="menu-item-text"> {{ item.name }}</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</template>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useSidebarStore } from '@/stores/modules/side-bar';
|
||||||
|
import router from '@/router';
|
||||||
|
|
||||||
|
const sidebarStore = useSidebarStore();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const selectedKey = computed(() => {
|
||||||
|
return [String(sidebarStore.activeMenuId)];
|
||||||
|
});
|
||||||
|
const menuList = computed(() => {
|
||||||
|
return sidebarStore.menuList;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDropdownClick = (item) => {
|
||||||
|
router.push({ name: item.routeName });
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import './style.scss';
|
||||||
|
</style>
|
||||||
|
<style lang="scss">
|
||||||
|
.layout-menu-item-dropdown {
|
||||||
|
.arco-dropdown {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--BG-300, #e6e6e8);
|
||||||
|
background: var(--BG-white, #fff);
|
||||||
|
padding: 12px 0px;
|
||||||
|
.arco-dropdown-option {
|
||||||
|
padding: 0 12px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
&-content {
|
||||||
|
display: flex;
|
||||||
|
height: 40px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 0;
|
||||||
|
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; /* 137.5% */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:not(.arco-dropdown-option-disabled):hover {
|
||||||
|
background: var(--BG-200, #f2f3f5);
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
background: var(--Brand-Brand-1, #f0edff) !important;
|
||||||
|
.menu-item-text {
|
||||||
|
color: var(--Brand-Brand-6, #6d4cfe) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
.navbar-menu {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 40px;
|
||||||
|
.menu-item-text {
|
||||||
|
color: var(--Text-2, #3c4043);
|
||||||
|
font-family: $font-family-medium;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
:deep(.arco-menu) {
|
||||||
|
height: 100%;
|
||||||
|
.arco-menu-inner {
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
.arco-menu-item {
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
&.arco-menu-selected {
|
||||||
|
.menu-item-text,
|
||||||
|
.arco-menu-selected-label {
|
||||||
|
color: #6d4cfe;
|
||||||
|
}
|
||||||
|
.arco-menu-selected-label {
|
||||||
|
background: var(--Brand-Brand-6, #6d4cfe);
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 50%;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -8px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.arco-icon-down {
|
||||||
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
.arco-dropdown-open .arco-icon-down {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
196
src/components/_base/navbar/components/right-side/index.vue
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
<template>
|
||||||
|
<div class="right-wrap">
|
||||||
|
<div class="relative mr-12px" @click="setUnread">
|
||||||
|
<SvgIcon
|
||||||
|
name="svg-taskCenter"
|
||||||
|
size="16"
|
||||||
|
class="cursor-pointer color-#737478 hover:color-#6D4CFE"
|
||||||
|
@click="openDownloadCenter"
|
||||||
|
/>
|
||||||
|
<div class="w-4px h-4px rounded-50% bg-#F64B31 absolute top-1px right-1px" v-if="hasUnreadInfo"></div>
|
||||||
|
</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">
|
||||||
|
<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>
|
||||||
|
<icon-right size="12" />
|
||||||
|
</a-space>
|
||||||
|
</a-doption>
|
||||||
|
<template #content>
|
||||||
|
<a-doption
|
||||||
|
v-for="(item, index) in enterprises"
|
||||||
|
:key="index"
|
||||||
|
class="rounded-8px hover:bg-#F2F3F5"
|
||||||
|
@click="onEnterpriseItemClick(item)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex items-center w-100% justify-between"
|
||||||
|
:class="enterpriseInfo?.id === item.id ? '!color-#6D4CFE' : ''"
|
||||||
|
>
|
||||||
|
<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">
|
||||||
|
<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>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
|
||||||
|
<ExitAccountModal ref="exitAccountModalRef" />
|
||||||
|
<DownloadCenterModal ref="downloadCenterModalRef" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import router from '@/router';
|
||||||
|
import { useEnterpriseStore } from '@/stores/modules/enterprise';
|
||||||
|
import { useSidebarStore } from '@/stores/modules/side-bar';
|
||||||
|
import { useUserStore } from '@/stores';
|
||||||
|
|
||||||
|
import ExitAccountModal from '@/components/_base/exit-account-modal';
|
||||||
|
import DownloadCenterModal from '../task-center-modal';
|
||||||
|
|
||||||
|
import icon1 from '@/assets/option.svg';
|
||||||
|
import icon2 from '@/assets/exit.svg';
|
||||||
|
import icon3 from '@/assets/change.svg';
|
||||||
|
|
||||||
|
const enterpriseStore = useEnterpriseStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const sideBarStore = useSidebarStore();
|
||||||
|
|
||||||
|
const hasUnreadInfo = computed(() => sideBarStore.unreadInfo.length);
|
||||||
|
|
||||||
|
const exitAccountModalRef = ref(null);
|
||||||
|
const downloadCenterModalRef = ref(null);
|
||||||
|
|
||||||
|
const enterprises = computed(() => {
|
||||||
|
return userStore.userInfo?.enterprises ?? [];
|
||||||
|
});
|
||||||
|
const enterpriseInfo = computed(() => {
|
||||||
|
return enterpriseStore?.enterpriseInfo ?? {};
|
||||||
|
});
|
||||||
|
|
||||||
|
const openDownloadCenter = () => {
|
||||||
|
downloadCenterModalRef.value.open();
|
||||||
|
};
|
||||||
|
const onEnterpriseItemClick = async (item) => {
|
||||||
|
enterpriseStore.setEnterpriseInfo(item);
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
|
const clickExit = async () => {
|
||||||
|
exitAccountModalRef.value?.open();
|
||||||
|
};
|
||||||
|
const setServerMenu = () => {
|
||||||
|
router.push('/management/person');
|
||||||
|
};
|
||||||
|
const setUnread = () => {
|
||||||
|
if (sideBarStore.unreadInfo.length) {
|
||||||
|
sideBarStore.removeTaskUnreadInfo();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.right-wrap {
|
||||||
|
display: flex;
|
||||||
|
padding-right: 20px;
|
||||||
|
list-style: none;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="scss">
|
||||||
|
.layout-avatar-dropdown,
|
||||||
|
.enterprises-dsubmenu {
|
||||||
|
.arco-dropdown {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--BG-300, #e6e6e8);
|
||||||
|
background: var(--BG-white, #fff);
|
||||||
|
padding: 12px 0px;
|
||||||
|
.arco-dropdown-option {
|
||||||
|
padding: 0 12px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
&-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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.arco-dropdown-option-content {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
&:not(.arco-dropdown-option-disabled):hover {
|
||||||
|
background-color: transparent;
|
||||||
|
.arco-dropdown-option-content {
|
||||||
|
background: var(--BG-200, #f2f3f5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.layout-avatar-dropdown,
|
||||||
|
.enterprises-dsubmenu {
|
||||||
|
width: 200px;
|
||||||
|
.arco-dropdown {
|
||||||
|
padding: 12px 4px;
|
||||||
|
.arco-dropdown-option {
|
||||||
|
padding: 0 !important;
|
||||||
|
&-content {
|
||||||
|
padding: 0 12px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.arco-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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
export const INITIAL_FORM = {
|
||||||
|
type: 1,
|
||||||
|
operator_name: '',
|
||||||
|
module: '',
|
||||||
|
sort_column: undefined,
|
||||||
|
sort_order: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TABLE_COLUMNS = [
|
||||||
|
{
|
||||||
|
title: '文件名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所属模块',
|
||||||
|
dataIndex: 'module',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
dataIndex: 'created_at',
|
||||||
|
width: 180,
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作人员',
|
||||||
|
dataIndex: 'operator.name',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
];
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="visible"
|
||||||
|
:title="isBatch ? '批量删除下载记录' : '删除下载记录'"
|
||||||
|
width="400px"
|
||||||
|
@close="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
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { deleteTask, deleteBatchTasks } from '@/api/all/common';
|
||||||
|
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
|
||||||
|
|
||||||
|
const emits = defineEmits(['update', 'close', 'batchUpdate']);
|
||||||
|
|
||||||
|
const visible = ref(false);
|
||||||
|
const taskId = ref(null);
|
||||||
|
const accountName = ref('');
|
||||||
|
|
||||||
|
const isBatch = computed(() => Array.isArray(taskId.value));
|
||||||
|
|
||||||
|
function onClose() {
|
||||||
|
visible.value = false;
|
||||||
|
taskId.value = null;
|
||||||
|
accountName.value = '';
|
||||||
|
emits('close');
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = (record) => {
|
||||||
|
const { id = null, name = '' } = record;
|
||||||
|
taskId.value = id;
|
||||||
|
accountName.value = name;
|
||||||
|
|
||||||
|
visible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function onDelete() {
|
||||||
|
const _fn = isBatch.value ? deleteBatchTasks : deleteTask;
|
||||||
|
const _params = isBatch.value ? { ids: taskId.value } : taskId.value;
|
||||||
|
|
||||||
|
const { code } = await _fn(_params);
|
||||||
|
if (code === 200) {
|
||||||
|
AMessage.success('删除成功');
|
||||||
|
isBatch.value ? emits('batchUpdate') : emits('update');
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open });
|
||||||
|
</script>
|
||||||
@ -0,0 +1,386 @@
|
|||||||
|
<script lang="jsx">
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { Input, Table, TableColumn, Checkbox, Pagination, Button, Tooltip, Notification } from '@arco-design/web-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';
|
||||||
|
import { EXPORT_TASK_STATUS, enumTaskStatus } from '../../constants';
|
||||||
|
import { formatTableField, exactFormatTime, genRandomId } from '@/utils/tools';
|
||||||
|
import { useTableSelectionWithPagination } from '@/hooks/useTableSelectionWithPagination';
|
||||||
|
import { downloadByUrl } from '@/utils/tools';
|
||||||
|
import DeleteTaskModal from './delete-task-modal.vue';
|
||||||
|
import icon1 from '@/assets/img/media-account/icon-delete.png';
|
||||||
|
import { showExportNotification, showFailExportNotification } from '@/utils/arcoD';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(props, { emit, expose }) {
|
||||||
|
const {
|
||||||
|
selectedRowKeys,
|
||||||
|
selectedRows,
|
||||||
|
dataSource,
|
||||||
|
pageInfo,
|
||||||
|
onPageChange,
|
||||||
|
onPageSizeChange,
|
||||||
|
rowSelection,
|
||||||
|
handleSelect,
|
||||||
|
handleSelectAll,
|
||||||
|
DEFAULT_PAGE_INFO,
|
||||||
|
} = useTableSelectionWithPagination({
|
||||||
|
onPageChange: () => {
|
||||||
|
getData();
|
||||||
|
},
|
||||||
|
onPageSizeChange: () => {
|
||||||
|
getData();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let queryTaskTimer = null;
|
||||||
|
|
||||||
|
const query = ref(cloneDeep(INITIAL_FORM));
|
||||||
|
const deleteTaskModalRef = ref(null);
|
||||||
|
const downloadTaskInfos = ref([]);
|
||||||
|
|
||||||
|
const checkedAll = computed(() => selectedRows.value.length === dataSource.value.length);
|
||||||
|
const indeterminate = computed(
|
||||||
|
() => selectedRows.value.length > 0 && selectedRows.value.length < dataSource.value.length,
|
||||||
|
);
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
query.value = cloneDeep(INITIAL_FORM);
|
||||||
|
pageInfo.value = cloneDeep(DEFAULT_PAGE_INFO);
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
dataSource.value = [];
|
||||||
|
downloadTaskInfos.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const { page, page_size } = pageInfo.value;
|
||||||
|
const { code, data } = await getTask({
|
||||||
|
...query.value,
|
||||||
|
page,
|
||||||
|
page_size,
|
||||||
|
});
|
||||||
|
if (code === 200) {
|
||||||
|
dataSource.value = data?.data ?? [];
|
||||||
|
pageInfo.value.total = data?.total;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const reload = () => {
|
||||||
|
pageInfo.value.page = 1;
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSorterChange = (column, order) => {
|
||||||
|
query.value.sort_column = column;
|
||||||
|
query.value.sort_order = order === 'ascend' ? 'asc' : 'desc';
|
||||||
|
reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearSelectedRows = () => {
|
||||||
|
selectedRows.value = [];
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownload = async (record) => {
|
||||||
|
if (record.status === enumTaskStatus.Failed) {
|
||||||
|
const { code } = await postRedoTask(record.id);
|
||||||
|
if (code === 200) {
|
||||||
|
showExportNotification(`正在下载“${record.name}”,请稍后...`);
|
||||||
|
getData();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
record.file && downloadByUrl(record.file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 批量下载
|
||||||
|
const handleBatchDownload = debounce(async () => {
|
||||||
|
const { code, data } = await postBatchDownload({ ids: selectedRowKeys.value });
|
||||||
|
if (code === 200) {
|
||||||
|
startBatchDownload(data.id);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
const startBatchDownload = (id) => {
|
||||||
|
const randomId = genRandomId();
|
||||||
|
showExportNotification(
|
||||||
|
`正在批量下载“${selectedRows.value[0]?.name}”等${selectedRows.value.length}个文件,请稍后...`,
|
||||||
|
{
|
||||||
|
duration: 0,
|
||||||
|
id: randomId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
downloadTaskInfos.value.push({
|
||||||
|
id,
|
||||||
|
randomId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!queryTaskTimer) {
|
||||||
|
queryTaskTimer = setInterval(() => getSyncTaskStatus(), 3000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const getSyncTaskStatus = async () => {
|
||||||
|
const { code, data } = await batchQueryTaskStatus({ ids: downloadTaskInfos.value.map((v) => v.id) });
|
||||||
|
if (code === 200) {
|
||||||
|
let completeTaskNum = 0;
|
||||||
|
data.forEach((item) => {
|
||||||
|
const { status, file, id } = item;
|
||||||
|
if (status !== 0) {
|
||||||
|
completeTaskNum++;
|
||||||
|
|
||||||
|
const notificationId = downloadTaskInfos.value.find((v) => v.id === id)?.randomId;
|
||||||
|
notificationId && Notification.remove(notificationId);
|
||||||
|
|
||||||
|
if (status === 1) {
|
||||||
|
AMessage.success('批量下载已完成,正在下载文件...');
|
||||||
|
downloadByUrl(file);
|
||||||
|
} else if (status === 2) {
|
||||||
|
const onReDownload = () => {
|
||||||
|
startBatchDownload(id);
|
||||||
|
};
|
||||||
|
showFailExportNotification(
|
||||||
|
`${selectedRows.value[0]?.name}”等${selectedRows.value.length}个文件下载失败`,
|
||||||
|
{ onReDownload },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 结束的任务过滤掉
|
||||||
|
downloadTaskInfos.value = downloadTaskInfos.value.filter((v) => v.id !== id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 全部完成了
|
||||||
|
if (completeTaskNum === data.length) {
|
||||||
|
clearQueryTaskTimer();
|
||||||
|
clearSelectedRows();
|
||||||
|
downloadTaskInfos.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (record) => {
|
||||||
|
const { id, name } = record;
|
||||||
|
deleteTaskModalRef.value.open({
|
||||||
|
id,
|
||||||
|
name: `“${name || '-'}”`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
const ids = selectedRows.value.map((item) => item.id);
|
||||||
|
const names = selectedRows.value.map((item) => `"${item.name || '-'}` + '"').join(',');
|
||||||
|
|
||||||
|
deleteTaskModalRef.value?.open({ id: ids, name: names });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBatchSuccess = () => {
|
||||||
|
selectedRows.value = [];
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearQueryTaskTimer = () => {
|
||||||
|
if (queryTaskTimer) {
|
||||||
|
clearInterval(queryTaskTimer);
|
||||||
|
queryTaskTimer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const unloadComp = () => {
|
||||||
|
clearQueryTaskTimer();
|
||||||
|
};
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearQueryTaskTimer;
|
||||||
|
});
|
||||||
|
|
||||||
|
expose({ init, reset, unloadComp });
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<div class="export-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={clearSelectedRows} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 表格 */}
|
||||||
|
<Table
|
||||||
|
ref="tableRef"
|
||||||
|
data={dataSource.value}
|
||||||
|
column-resizable
|
||||||
|
row-key="id"
|
||||||
|
row-selection={rowSelection.value}
|
||||||
|
selected-keys={selectedRowKeys.value}
|
||||||
|
pagination={false}
|
||||||
|
scroll={{ x: '100%', y: '100%' }}
|
||||||
|
class="w-100% flex-1 overflow-hidden"
|
||||||
|
bordered
|
||||||
|
onSorterChange={handleSorterChange}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
onSelectAll={handleSelectAll}
|
||||||
|
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>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 分页 */}
|
||||||
|
{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
|
||||||
|
current={pageInfo.value.page}
|
||||||
|
page-size={pageInfo.value.page_size}
|
||||||
|
onChange={onPageChange}
|
||||||
|
onPageSizeChange={onPageSizeChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 删除弹窗 */}
|
||||||
|
<DeleteTaskModal ref={deleteTaskModalRef} onBatchUpdate={onBatchSuccess} onUpdate={getData} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './style.scss';
|
||||||
|
</style>
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
.export-task-wrap {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.tip-row {
|
||||||
|
border-radius: 2px;
|
||||||
|
background: #f0edff;
|
||||||
|
.label {
|
||||||
|
font-family: $font-family-medium;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
&.normal {
|
||||||
|
background: #ebf7f2;
|
||||||
|
.label {
|
||||||
|
color: #211f24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.abnormal {
|
||||||
|
background: #ffe7e4;
|
||||||
|
.label {
|
||||||
|
color: #211f24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.err-btn {
|
||||||
|
background-color: #f64b31 !important;
|
||||||
|
color: var(--BG-white, #fff);
|
||||||
|
font-family: 'PingFang SC';
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
.operation-btn {
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--Brand-Brand-6, #6d4cfe);
|
||||||
|
font-family: $font-family-regular;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
&.red {
|
||||||
|
color: #f64b31;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.status-box {
|
||||||
|
border-radius: 2px;
|
||||||
|
display: flex;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0px 8px;
|
||||||
|
align-items: center;
|
||||||
|
width: fit-content;
|
||||||
|
span {
|
||||||
|
font-family: $font-family-medium;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
&-0 {
|
||||||
|
background: var(--Functional-yellow-1, #fff7e5);
|
||||||
|
span {
|
||||||
|
color: var(--Functional-yellow-6, #ffae00);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-1 {
|
||||||
|
background: var(--Functional-Green-1, #ebf7f2);
|
||||||
|
span {
|
||||||
|
color: var(--Functional-Green-6, #25c883);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-2 {
|
||||||
|
background: var(--Functional-Red-1, #ffe9e7);
|
||||||
|
span {
|
||||||
|
color: var(--Functional-Red-6, #f64b31);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
export const INITIAL_FORM = {
|
||||||
|
type: 0,
|
||||||
|
operator_name: '',
|
||||||
|
module: '',
|
||||||
|
sort_column: undefined,
|
||||||
|
sort_order: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TABLE_COLUMNS = [
|
||||||
|
{
|
||||||
|
title: '任务名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所属模块',
|
||||||
|
dataIndex: 'module',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '共导入',
|
||||||
|
dataIndex: 'total_number',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '导入成功',
|
||||||
|
dataIndex: 'success_number',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '导入失败',
|
||||||
|
dataIndex: 'fail_number',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '导入时间',
|
||||||
|
dataIndex: 'created_at',
|
||||||
|
width: 180,
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作人员',
|
||||||
|
dataIndex: 'operator.name',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
];
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="visible"
|
||||||
|
:title="isBatch ? '批量删除导入记录' : '删除导入记录'"
|
||||||
|
width="400px"
|
||||||
|
@close="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
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { deleteTask, deleteBatchTasks } from '@/api/all/common';
|
||||||
|
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
|
||||||
|
|
||||||
|
const emits = defineEmits(['update', 'close', 'batchUpdate']);
|
||||||
|
|
||||||
|
const visible = ref(false);
|
||||||
|
const taskId = ref(null);
|
||||||
|
const accountName = ref('');
|
||||||
|
|
||||||
|
const isBatch = computed(() => Array.isArray(taskId.value));
|
||||||
|
|
||||||
|
function onClose() {
|
||||||
|
visible.value = false;
|
||||||
|
taskId.value = null;
|
||||||
|
accountName.value = '';
|
||||||
|
emits('close');
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = (record) => {
|
||||||
|
const { id = null, name = '' } = record;
|
||||||
|
taskId.value = id;
|
||||||
|
accountName.value = name;
|
||||||
|
|
||||||
|
visible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function onDelete() {
|
||||||
|
const _fn = isBatch.value ? deleteBatchTasks : deleteTask;
|
||||||
|
const _params = isBatch.value ? { ids: taskId.value } : taskId.value;
|
||||||
|
const { code } = await _fn(_params);
|
||||||
|
if (code === 200) {
|
||||||
|
AMessage.success('删除成功');
|
||||||
|
isBatch.value ? emits('batchUpdate') : emits('update');
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open });
|
||||||
|
</script>
|
||||||
@ -0,0 +1,300 @@
|
|||||||
|
<script lang="jsx">
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { Input, Table, TableColumn, Checkbox, Pagination, Button, Tooltip, Notification } from '@arco-design/web-vue';
|
||||||
|
import { IconSearch, IconClose, IconQuestionCircle } from '@arco-design/web-vue/es/icon';
|
||||||
|
import NoData from '@/components/no-data';
|
||||||
|
import { getTask } from '@/api/all/common';
|
||||||
|
import { INITIAL_FORM, TABLE_COLUMNS } from './constants';
|
||||||
|
import { IMPORT_TASK_STATUS, enumTaskStatus } from '../../constants';
|
||||||
|
import { formatTableField, exactFormatTime } from '@/utils/tools';
|
||||||
|
import { useTableSelectionWithPagination } from '@/hooks/useTableSelectionWithPagination';
|
||||||
|
import { downloadByUrl } from '@/utils/tools';
|
||||||
|
import DeleteTaskModal from './delete-task-modal.vue';
|
||||||
|
import icon1 from '@/assets/img/media-account/icon-delete.png';
|
||||||
|
// import { showExportNotification } from '@/utils/arcoD';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(props, { emit, expose }) {
|
||||||
|
const {
|
||||||
|
selectedRowKeys,
|
||||||
|
selectedRows,
|
||||||
|
dataSource,
|
||||||
|
pageInfo,
|
||||||
|
onPageChange,
|
||||||
|
onPageSizeChange,
|
||||||
|
rowSelection,
|
||||||
|
handleSelect,
|
||||||
|
handleSelectAll,
|
||||||
|
DEFAULT_PAGE_INFO,
|
||||||
|
} = useTableSelectionWithPagination({
|
||||||
|
onPageChange: () => {
|
||||||
|
getData();
|
||||||
|
},
|
||||||
|
onPageSizeChange: () => {
|
||||||
|
getData();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const query = ref({ ...INITIAL_FORM });
|
||||||
|
const deleteTaskModalRef = ref(null);
|
||||||
|
|
||||||
|
const checkedAll = computed(() => selectedRows.value.length === dataSource.value.length);
|
||||||
|
const indeterminate = computed(
|
||||||
|
() => selectedRows.value.length > 0 && selectedRows.value.length < dataSource.value.length,
|
||||||
|
);
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
query.value = { ...INITIAL_FORM };
|
||||||
|
pageInfo.value = { ...DEFAULT_PAGE_INFO };
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
selectedRows.value = [];
|
||||||
|
dataSource.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const { page, page_size } = pageInfo.value;
|
||||||
|
const { code, data } = await getTask({
|
||||||
|
...query.value,
|
||||||
|
page,
|
||||||
|
page_size,
|
||||||
|
});
|
||||||
|
if (code === 200) {
|
||||||
|
dataSource.value = data?.data ?? [];
|
||||||
|
pageInfo.value.total = data?.total;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const reload = () => {
|
||||||
|
pageInfo.value.page = 1;
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSorterChange = (column, order) => {
|
||||||
|
query.value.sort_column = column;
|
||||||
|
query.value.sort_order = order === 'ascend' ? 'asc' : 'desc';
|
||||||
|
reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseTip = () => {
|
||||||
|
selectedRows.value = [];
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownload = (record) => {
|
||||||
|
downloadByUrl(record.file);
|
||||||
|
// showExportNotification(`正在下载“${record.name}”,请稍后...`)
|
||||||
|
};
|
||||||
|
const handleBatchDownload = () => {
|
||||||
|
// 批量下载逻辑
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (record) => {
|
||||||
|
const { id, name } = record;
|
||||||
|
deleteTaskModalRef.value.open({
|
||||||
|
id,
|
||||||
|
name: `“${name || '-'}”`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
const ids = selectedRows.value.map((item) => item.id);
|
||||||
|
const names = selectedRows.value.map((item) => `"${item.name || '-'}` + '"').join(',');
|
||||||
|
deleteTaskModalRef.value?.open({ id: ids, name: names });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBatchSuccess = () => {
|
||||||
|
selectedRows.value = [];
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
expose({ init, reset });
|
||||||
|
|
||||||
|
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}
|
||||||
|
pagination={false}
|
||||||
|
scroll={{ x: '100%', y: '100%' }}
|
||||||
|
class="w-100% flex-1 overflow-hidden"
|
||||||
|
bordered
|
||||||
|
onSorterChange={handleSorterChange}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
onSelectAll={handleSelectAll}
|
||||||
|
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>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 分页 */}
|
||||||
|
{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
|
||||||
|
current={pageInfo.value.page}
|
||||||
|
page-size={pageInfo.value.page_size}
|
||||||
|
onChange={onPageChange}
|
||||||
|
onPageSizeChange={onPageSizeChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 删除弹窗 */}
|
||||||
|
<DeleteTaskModal ref={deleteTaskModalRef} onBatchUpdate={onBatchSuccess} onUpdate={getData} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './style.scss';
|
||||||
|
</style>
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
.import-task-wrap {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.tip-row {
|
||||||
|
border-radius: 2px;
|
||||||
|
background: #f0edff;
|
||||||
|
.label {
|
||||||
|
font-family: $font-family-medium;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
&.normal {
|
||||||
|
background: #ebf7f2;
|
||||||
|
.label {
|
||||||
|
color: #211f24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.abnormal {
|
||||||
|
background: #ffe7e4;
|
||||||
|
.label {
|
||||||
|
color: #211f24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.err-btn {
|
||||||
|
background-color: #f64b31 !important;
|
||||||
|
color: var(--BG-white, #fff);
|
||||||
|
font-family: 'PingFang SC';
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
.operation-btn {
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--Brand-Brand-6, #6d4cfe);
|
||||||
|
font-family: $font-family-regular;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
&.red {
|
||||||
|
color: #f64b31;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.status-box {
|
||||||
|
border-radius: 2px;
|
||||||
|
display: flex;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0px 8px;
|
||||||
|
align-items: center;
|
||||||
|
width: fit-content;
|
||||||
|
span {
|
||||||
|
font-family: $font-family-medium;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
&-0 {
|
||||||
|
background: var(--Functional-yellow-1, #fff7e5);
|
||||||
|
span {
|
||||||
|
color: var(--Functional-yellow-6, #ffae00);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-1 {
|
||||||
|
background: var(--Functional-Green-1, #ebf7f2);
|
||||||
|
span {
|
||||||
|
color: var(--Functional-Green-6, #25c883);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-2 {
|
||||||
|
background: var(--Functional-Red-1, #ffe9e7);
|
||||||
|
span {
|
||||||
|
color: var(--Functional-Red-6, #f64b31);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
export enum enumTaskStatus {
|
||||||
|
Exporting = 0, // 导出中
|
||||||
|
Finished = 1, // 已完成
|
||||||
|
Failed = 2, // 导出失败
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EXPORT_TASK_STATUS = [
|
||||||
|
{
|
||||||
|
label: '导出中',
|
||||||
|
value: enumTaskStatus.Exporting,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '已完成',
|
||||||
|
value: enumTaskStatus.Finished,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '导出失败',
|
||||||
|
value: enumTaskStatus.Failed,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const IMPORT_TASK_STATUS = [
|
||||||
|
{
|
||||||
|
label: '导入中',
|
||||||
|
value: enumTaskStatus.Exporting,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '已完成',
|
||||||
|
value: enumTaskStatus.Finished,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '导入失败',
|
||||||
|
value: enumTaskStatus.Failed,
|
||||||
|
},
|
||||||
|
];
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="visible"
|
||||||
|
title="任务中心"
|
||||||
|
modal-class="task-center-modal"
|
||||||
|
width="860px"
|
||||||
|
:mask-closable="false"
|
||||||
|
:footer="false"
|
||||||
|
@close="onClose"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
<div class="content">
|
||||||
|
<component :is="activeTab === '0' ? ImportTask : ExportTask" ref="componentRef" />
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { Notification } from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
import ExportTask from './components/export-task';
|
||||||
|
import ImportTask from './components/import-task';
|
||||||
|
|
||||||
|
const visible = ref(false);
|
||||||
|
const componentRef = ref(null);
|
||||||
|
const activeTab = ref('0');
|
||||||
|
|
||||||
|
let timer = null;
|
||||||
|
|
||||||
|
const handleTabClick = (key) => {
|
||||||
|
activeTab.value = key;
|
||||||
|
nextTick(() => {
|
||||||
|
getData();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = () => {
|
||||||
|
componentRef.value?.init();
|
||||||
|
};
|
||||||
|
|
||||||
|
const open = () => {
|
||||||
|
getData();
|
||||||
|
|
||||||
|
timer = setInterval(() => {
|
||||||
|
getData();
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
visible.value = true;
|
||||||
|
};
|
||||||
|
const onClose = () => {
|
||||||
|
activeTab.value = '0';
|
||||||
|
|
||||||
|
clearTimer();
|
||||||
|
componentRef.value?.unloadComp?.();
|
||||||
|
Notification.clear();
|
||||||
|
visible.value = false;
|
||||||
|
};
|
||||||
|
const clearTimer = () => {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer);
|
||||||
|
timer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ open });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import './style.scss';
|
||||||
|
</style>
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
.task-center-modal {
|
||||||
|
.arco-modal-header {
|
||||||
|
border-bottom: none !important;
|
||||||
|
.arco-modal-title {
|
||||||
|
color: var(--Text-1, #211f24);
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 24px;
|
||||||
|
font-family: $font-family-medium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.arco-modal-body {
|
||||||
|
padding: 0 !important;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 650px;
|
||||||
|
.filter-row {
|
||||||
|
.filter-row-item {
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-right: 24px;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
margin-right: 12px;
|
||||||
|
color: #211f24;
|
||||||
|
font-family: $font-family-regular;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
flex-shrink: 0;
|
||||||
|
line-height: 22px; /* 157.143% */
|
||||||
|
}
|
||||||
|
:deep(.arco-space-item) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.arco-tabs {
|
||||||
|
.arco-tabs-nav-top {
|
||||||
|
.arco-tabs-tab {
|
||||||
|
margin: 0 20px;
|
||||||
|
.arco-tabs-tab-title {
|
||||||
|
color: var(--Text-2, #3c4043);
|
||||||
|
font-family: $font-family-regular;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
&.arco-tabs-tab-active {
|
||||||
|
.arco-tabs-tab-title {
|
||||||
|
color: #6D4CFE;
|
||||||
|
font-family: $font-family-medium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.arco-tabs-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 24px 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cts {
|
||||||
|
color: var(--Text-1, #211f24);
|
||||||
|
font-family: $font-family-medium;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,257 +1,49 @@
|
|||||||
<script setup>
|
|
||||||
import { useAppStore } from '@/stores';
|
|
||||||
import { useEnterpriseStore } from '@/stores/modules/enterprise';
|
|
||||||
// import { IconExport, IconFile, IconCaretDown } from '@arco-design/web-vue/es/icon';
|
|
||||||
// import { handleUserLogout } from '@/utils/user';
|
|
||||||
// import { fetchLogOut } from '@/api/all/login';
|
|
||||||
import { useSidebarStore } from '@/stores/modules/side-bar';
|
|
||||||
import { MENU_GROUP_IDS } from '@/router/constants';
|
|
||||||
import router from '@/router';
|
|
||||||
// import { useRoute } from 'vue-router';
|
|
||||||
import ExitAccountModal from '@/components/_base/exit-account-modal/index.vue';
|
|
||||||
// import { appRoutes } from '@/router/routes';
|
|
||||||
// import { MENU_LIST } from './constants';
|
|
||||||
|
|
||||||
const sidebarStore = useSidebarStore();
|
|
||||||
// const enterpriseStore = useEnterpriseStore();
|
|
||||||
// const route = useRoute();
|
|
||||||
const exitAccountModalRef = ref(null);
|
|
||||||
// const selectedKey = ref([]);
|
|
||||||
|
|
||||||
const selectedKey = computed(() => {
|
|
||||||
return [String(sidebarStore.activeMenuId)];
|
|
||||||
});
|
|
||||||
const menuList = computed(() => {
|
|
||||||
return sidebarStore.menuList;
|
|
||||||
});
|
|
||||||
|
|
||||||
const clickExit = async () => {
|
|
||||||
exitAccountModalRef.value?.open();
|
|
||||||
};
|
|
||||||
|
|
||||||
// const appStore = useAppStore();
|
|
||||||
|
|
||||||
const setServerMenu = () => {
|
|
||||||
router.push('/management/person');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDopdownClick = (item) => {
|
|
||||||
router.push({ name: item.routeName });
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="navbar">
|
<div class="navbar-wrap">
|
||||||
<div class="left-side">
|
<div class="left-wrap">
|
||||||
<a-space class="cursor-pointer" @click="router.push('/')">
|
<a-space class="cursor-pointer" @click="router.push('/')">
|
||||||
<img src="@/assets/logo.svg" alt="" />
|
<img src="@/assets/LOGO.svg" alt="" />
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="center-side">
|
<div class="flex-1">
|
||||||
<div class="menu-demo h-100%">
|
<NavbarMenu />
|
||||||
<a-menu mode="horizontal" :selected-keys="selectedKey">
|
|
||||||
<a-menu-item v-for="item in menuList" :key="String(item.id)">
|
|
||||||
<template v-if="item.children">
|
|
||||||
<a-dropdown :popup-max-height="false" class="layout-menu-item-dropdown">
|
|
||||||
<a-button>
|
|
||||||
<span class="menu-item-text mr-2px"> {{ item.name }}</span>
|
|
||||||
<icon-caret-down size="16" class="arco-icon-down !mr-0" />
|
|
||||||
</a-button>
|
|
||||||
<template #content>
|
|
||||||
<a-doption v-for="(child, ind) in item.children" :key="ind" @click="handleDopdownClick(child)">
|
|
||||||
<span class="menu-item-text"> {{ child.name }}</span>
|
|
||||||
</a-doption>
|
|
||||||
</template>
|
|
||||||
</a-dropdown>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<a-menu-item :key="String(item.id)" @click="handleDopdownClick(item)">
|
|
||||||
<span class="menu-item-text"> {{ item.name }}</span>
|
|
||||||
</a-menu-item>
|
|
||||||
</template>
|
|
||||||
</a-menu-item>
|
|
||||||
</a-menu>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<RightSide />
|
||||||
<ul class="right-side">
|
|
||||||
<li>
|
|
||||||
<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">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<img src="@/assets/option.svg" class="w-16px h-16px mr-8px" />
|
|
||||||
<span>管理中心</span>
|
|
||||||
</div>
|
|
||||||
<icon-right size="12" />
|
|
||||||
</a-space>
|
|
||||||
</a-doption>
|
|
||||||
<!-- <a-doption>
|
|
||||||
<a-space class="flex justify-between w-100%">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<img src="@/assets/change.svg" class="w-16px h-16px mr-8px" />
|
|
||||||
<span>切换企业账号</span>
|
|
||||||
</div>
|
|
||||||
<icon-right size="12" />
|
|
||||||
</a-space>
|
|
||||||
</a-doption> -->
|
|
||||||
<a-doption>
|
|
||||||
<a-space class="flex justify-between w-100%" @click="clickExit">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<img src="@/assets/exit.svg" class="w-16px h-16px mr-8px" />
|
|
||||||
<span>退出登录</span>
|
|
||||||
</div>
|
|
||||||
<icon-right size="12" />
|
|
||||||
</a-space>
|
|
||||||
</a-doption>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</a-dropdown>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ExitAccountModal ref="exitAccountModalRef" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import NavbarMenu from './components/navbar-menu';
|
||||||
|
import RightSide from './components/right-side';
|
||||||
|
|
||||||
|
import router from '@/router';
|
||||||
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.navbar {
|
.navbar-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: var(--color-bg-2);
|
background-color: var(--color-bg-2);
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
}
|
.left-wrap {
|
||||||
.left-side {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
.center-side {
|
.arco-dropdown-option-suffix {
|
||||||
flex: 1;
|
display: none;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
margin-left: 40px;
|
|
||||||
.menu-item-text {
|
|
||||||
color: var(--Text-2, #3c4043);
|
|
||||||
font-family: $font-family-medium;
|
|
||||||
font-size: 16px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 22px;
|
|
||||||
}
|
}
|
||||||
:deep(.arco-menu) {
|
.enterprises-doption {
|
||||||
height: 100%;
|
.arco-dropdown-option-content {
|
||||||
.arco-menu-inner {
|
padding: 0 !important;
|
||||||
padding: 0 20px;
|
|
||||||
}
|
|
||||||
.arco-menu-item {
|
|
||||||
padding: 0;
|
|
||||||
position: relative;
|
|
||||||
&.arco-menu-selected {
|
|
||||||
.menu-item-text,
|
|
||||||
.arco-menu-selected-label {
|
|
||||||
color: #6d4cfe;
|
|
||||||
}
|
|
||||||
.arco-menu-selected-label {
|
|
||||||
background: var(--Brand-Brand-6, #6d4cfe);
|
|
||||||
height: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
width: 50%;
|
|
||||||
position: absolute;
|
|
||||||
bottom: -8px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.arco-icon-down {
|
|
||||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
}
|
|
||||||
.arco-dropdown-open .arco-icon-down {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.cneter-tip {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--color-text-1);
|
|
||||||
}
|
|
||||||
.menu-demo {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.right-side {
|
|
||||||
display: flex;
|
|
||||||
padding-right: 20px;
|
|
||||||
list-style: none;
|
|
||||||
li {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 10px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: var(--color-text-1);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.nav-btn {
|
|
||||||
border-color: rgb(var(--gray-2));
|
|
||||||
color: rgb(var(--gray-8));
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.layout-menu-item-dropdown,
|
|
||||||
.layout-avatar-dropdown {
|
|
||||||
.arco-dropdown {
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid var(--BG-300, #e6e6e8);
|
|
||||||
background: var(--BG-white, #fff);
|
|
||||||
padding: 12px 0px;
|
|
||||||
.arco-dropdown-option {
|
|
||||||
padding: 0 12px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
&-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; /* 137.5% */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&:not(.arco-dropdown-option-disabled):hover {
|
&:not(.arco-dropdown-option-disabled):hover {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
.arco-dropdown-option-content {
|
.arco-dropdown-option-content {
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--BG-200, #f2f3f5);
|
background: var(--BG-200, #f2f3f5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.layout-avatar-dropdown {
|
|
||||||
width: 200px;
|
|
||||||
.arco-dropdown {
|
|
||||||
padding: 12px 4px;
|
|
||||||
.arco-dropdown-option {
|
|
||||||
padding: 0 !important;
|
|
||||||
&-content {
|
|
||||||
padding: 0 12px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -4,11 +4,12 @@
|
|||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<a-select
|
<a-select
|
||||||
v-model="selectedTags"
|
v-model="selectedValues"
|
||||||
:multiple="multiple"
|
:multiple="multiple"
|
||||||
size="medium"
|
size="medium"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
allow-clear
|
allow-clear
|
||||||
|
:max-tag-count="maxTagCount"
|
||||||
@change="handleChange"
|
@change="handleChange"
|
||||||
>
|
>
|
||||||
<a-option v-for="(item, index) in options" :key="index" :value="item.id" :label="item.name">
|
<a-option v-for="(item, index) in options" :key="index" :value="item.id" :label="item.name">
|
||||||
@ -37,28 +38,30 @@ const props = defineProps({
|
|||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
maxTagCount: {
|
||||||
|
type: Number,
|
||||||
|
default: 3,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emits = defineEmits(['update:modelValue', 'change']);
|
const emits = defineEmits(['update:modelValue', 'change']);
|
||||||
|
|
||||||
const selectedTags = ref(props.multiple ? [] : '');
|
const selectedValues = ref(props.multiple ? [] : '');
|
||||||
|
|
||||||
// 监听外部传入的值变化
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
selectedTags.value = newVal;
|
selectedValues.value = newVal;
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
// 监听内部值变化,向外部发送更新
|
watch(selectedValues, (newVal) => {
|
||||||
watch(selectedTags, (newVal) => {
|
|
||||||
emits('update:modelValue', newVal);
|
emits('update:modelValue', newVal);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleChange = (value) => {
|
const handleChange = (value) => {
|
||||||
selectedTags.value = value;
|
selectedValues.value = value;
|
||||||
emits('change', value);
|
emits('change', value);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -65,7 +65,7 @@
|
|||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div style="text-align: right">
|
<div style="text-align: right">
|
||||||
<a-button class="mr-8px cancel-btn" size="medium" @click="close">取消</a-button>
|
<a-button class="mr-8px" size="medium" @click="close">取消</a-button>
|
||||||
<a-button type="primary" size="medium" @click="onSubmit">确定</a-button>
|
<a-button type="primary" size="medium" @click="onSubmit">确定</a-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -87,13 +87,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.arco-modal-footer {
|
.arco-modal-footer {
|
||||||
.cancel-btn {
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid var(--BG-500, #b1b2b5);
|
|
||||||
background: var(--BG-white, #fff);
|
|
||||||
&:hover {
|
|
||||||
border: 1px solid var(--BG-500, #b1b2b5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { ref, onMounted } from 'vue';
|
|||||||
import { getQueryParam } from '@/utils/helper';
|
import { getQueryParam } from '@/utils/helper';
|
||||||
|
|
||||||
import { getEnterpriseByInviteCode, joinEnterpriseByInviteCode } from '@/api/all';
|
import { getEnterpriseByInviteCode, joinEnterpriseByInviteCode } from '@/api/all';
|
||||||
|
|
||||||
const enterprise = ref();
|
const enterprise = ref();
|
||||||
const inviteCode = ref();
|
const inviteCode = ref();
|
||||||
|
|
||||||
@ -27,8 +28,11 @@ async function handleJoin() {
|
|||||||
await joinEnterpriseByInviteCode(inviteCode.value);
|
await joinEnterpriseByInviteCode(inviteCode.value);
|
||||||
AMessage.success('加入成功');
|
AMessage.success('加入成功');
|
||||||
}
|
}
|
||||||
onMounted(() => {
|
// onMounted(() => {
|
||||||
getEnterprise();
|
// getEnterprise();
|
||||||
|
// });
|
||||||
|
defineExpose({
|
||||||
|
getEnterprise,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
29
src/components/svg-icon/index.vue
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<svg aria-hidden="true" class="svg-icon" :width="props.size" :height="props.size">
|
||||||
|
<use :xlink:href="symbolId" :fill="props.color" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
const props = defineProps({
|
||||||
|
prefix: {
|
||||||
|
type: String,
|
||||||
|
default: 'icon',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#333',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: '12',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const symbolId = computed(() => `#${props.prefix}-${props.name}`);
|
||||||
|
</script>
|
||||||
96
src/hooks/useTableSelectionWithPagination.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { ref, computed } from 'vue';
|
||||||
|
|
||||||
|
interface UseTableSelectionWithPaginationOptions {
|
||||||
|
rowKey?: string; // 主键字段名,默认 'id'
|
||||||
|
pageInfo?: {
|
||||||
|
page?: number;
|
||||||
|
page_size?: number;
|
||||||
|
total?: number;
|
||||||
|
};
|
||||||
|
onPageChange?: (page: number) => void;
|
||||||
|
onPageSizeChange?: (size: number) => void;
|
||||||
|
onSelectChange?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_PAGE_INFO = {
|
||||||
|
page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useTableSelectionWithPagination(options: UseTableSelectionWithPaginationOptions = {}) {
|
||||||
|
const rowKey = options.rowKey || 'id';
|
||||||
|
|
||||||
|
const selectedRowKeys = ref<Array<string | number>>([]);
|
||||||
|
const selectedRows = ref<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;
|
||||||
|
|
||||||
|
if (select) {
|
||||||
|
if (!selectedRows.value.some((v) => v[rowKey] === record[rowKey])) {
|
||||||
|
selectedRows.value.push(record);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedRows.value = selectedRows.value.filter((v) => v[rowKey] !== record[rowKey]);
|
||||||
|
}
|
||||||
|
options.onSelectChange?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 全选/取消全选
|
||||||
|
const handleSelectAll = (checked: boolean) => {
|
||||||
|
const currentPageRows = dataSource.value;
|
||||||
|
const currentPageKeys = currentPageRows.map((v) => v[rowKey]);
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
selectedRowKeys.value = Array.from(new Set([...selectedRowKeys.value, ...currentPageKeys]));
|
||||||
|
const allRows = [...selectedRows.value, ...currentPageRows];
|
||||||
|
const map = new Map();
|
||||||
|
allRows.forEach((row) => map.set(row[rowKey], row));
|
||||||
|
selectedRows.value = Array.from(map.values());
|
||||||
|
} else {
|
||||||
|
selectedRowKeys.value = selectedRowKeys.value.filter((key) => !currentPageKeys.includes(key));
|
||||||
|
selectedRows.value = selectedRows.value.filter((row) => !currentPageKeys.includes(row[rowKey]));
|
||||||
|
}
|
||||||
|
options.onSelectChange?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPageChange = (page: number) => {
|
||||||
|
pageInfo.value.page = page;
|
||||||
|
options.onPageChange?.(page);
|
||||||
|
};
|
||||||
|
const onPageSizeChange = (size: number) => {
|
||||||
|
pageInfo.value.page_size = size;
|
||||||
|
pageInfo.value.page = 1;
|
||||||
|
options.onPageSizeChange?.(size);
|
||||||
|
};
|
||||||
|
const resetPageInfo = () => {
|
||||||
|
pageInfo.value = cloneDeep(DEFAULT_PAGE_INFO)
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowSelection = computed(() => ({
|
||||||
|
type: 'checkbox',
|
||||||
|
showCheckedAll: true,
|
||||||
|
width: 48,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedRowKeys,
|
||||||
|
selectedRows,
|
||||||
|
dataSource,
|
||||||
|
pageInfo,
|
||||||
|
onPageChange,
|
||||||
|
onPageSizeChange,
|
||||||
|
rowSelection,
|
||||||
|
handleSelect,
|
||||||
|
handleSelectAll,
|
||||||
|
resetPageInfo,
|
||||||
|
DEFAULT_PAGE_INFO,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,13 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { useAppStore } from '@/stores';
|
import { useAppStore } from '@/stores';
|
||||||
import { useResponsive } from '@/hooks';
|
import { useResponsive } from '@/hooks';
|
||||||
import JoinModal from '@/components/join-modal.vue';
|
import JoinModal from '@/components/join-modal.vue';
|
||||||
import { getQueryParam } from '@/utils/helper';
|
import { getQueryParam } from '@/utils/helper';
|
||||||
|
import { useUserStore } from '@/stores';
|
||||||
|
|
||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
const joinEnterpriseVisible = ref(false);
|
const joinEnterpriseVisible = ref(false);
|
||||||
|
const joinModalRef = ref(null);
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
@ -36,14 +40,15 @@ const paddingStyle = computed(() => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkHasInviteCode();
|
checkHasInviteCode();
|
||||||
});
|
});
|
||||||
const setCollapsed = (val: boolean) => {
|
const setCollapsed = (val) => {
|
||||||
appStore.updateSettings({ menuCollapse: val });
|
appStore.updateSettings({ menuCollapse: val });
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkHasInviteCode = () => {
|
const checkHasInviteCode = () => {
|
||||||
const inviteCode = getQueryParam('invite_code');
|
const inviteCode = getQueryParam('invite_code');
|
||||||
if (inviteCode) {
|
if (userStore.isLogin && inviteCode) {
|
||||||
joinEnterpriseVisible.value = true;
|
joinEnterpriseVisible.value = true;
|
||||||
|
joinModalRef.value?.getEnterprise?.();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const drawerVisible = ref(false);
|
const drawerVisible = ref(false);
|
||||||
@ -57,7 +62,7 @@ provide('toggleDrawerMenu', () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a-layout :class="['layout', { mobile: appStore.hideMenu }]">
|
<a-layout :class="['layout', { mobile: appStore.hideMenu }]">
|
||||||
<JoinModal v-model:visible="joinEnterpriseVisible" />
|
<JoinModal v-model:visible="joinEnterpriseVisible" ref="joinModalRef" />
|
||||||
<div v-if="navbar" class="layout-navbar">
|
<div v-if="navbar" class="layout-navbar">
|
||||||
<base-navbar />
|
<base-navbar />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
13
src/main.ts
@ -5,18 +5,25 @@
|
|||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
import store from './stores';
|
import store from './stores';
|
||||||
import NoData from '@/components/no-data';
|
|
||||||
import '@/api/index';
|
|
||||||
|
|
||||||
|
import NoData from '@/components/no-data';
|
||||||
|
import SvgIcon from "@/components/svg-icon";
|
||||||
|
|
||||||
|
import '@/api/index';
|
||||||
import '@arco-design/web-vue/dist/arco.css'; // Arco 默认样式
|
import '@arco-design/web-vue/dist/arco.css'; // Arco 默认样式
|
||||||
import './core';
|
import './core';
|
||||||
|
|
||||||
import 'normalize.css';
|
import 'normalize.css';
|
||||||
import 'uno.css';
|
import 'uno.css';
|
||||||
|
import 'virtual:svg-icons-register'
|
||||||
|
|
||||||
// import '@/styles/vars.css'; // 优先加载
|
// import '@/styles/vars.css'; // 优先加载
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
|
app.component('NoData', NoData);
|
||||||
|
app.component('SvgIcon', SvgIcon);
|
||||||
|
|
||||||
app.use(store);
|
app.use(store);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.component('NoData', NoData);
|
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
|||||||
@ -18,9 +18,10 @@ export default function setupUserLoginInfoGuard(router: Router) {
|
|||||||
const routeName = to?.name as string;
|
const routeName = to?.name as string;
|
||||||
const requiresAuth = to?.meta?.requiresAuth || false;
|
const requiresAuth = to?.meta?.requiresAuth || false;
|
||||||
const requireLogin = to?.meta?.requireLogin || false;
|
const requireLogin = to?.meta?.requireLogin || false;
|
||||||
|
const query = to?.query ?? {};
|
||||||
|
|
||||||
if (requireLogin && !userStore.isLogin) {
|
if (requireLogin && !userStore.isLogin) {
|
||||||
goUserLogin();
|
goUserLogin(query);
|
||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,10 +5,11 @@
|
|||||||
import type { AppRouteRecordRaw } from '../types';
|
import type { AppRouteRecordRaw } from '../types';
|
||||||
import { MENU_GROUP_IDS } from '@/router/constants';
|
import { MENU_GROUP_IDS } from '@/router/constants';
|
||||||
|
|
||||||
import IconRepository from '@/assets/svg/icon-repository.svg';
|
import IconRepository from '@/assets/svg/svg-repository.svg';
|
||||||
import IconMediaAccount from '@/assets/svg/icon-mediaAccount.svg';
|
import IconMediaAccount from '@/assets/svg/svg-mediaAccount.svg';
|
||||||
import IconPutAccount from '@/assets/svg/icon-putAccount.svg';
|
import IconPutAccount from '@/assets/svg/svg-putAccount.svg';
|
||||||
import IconIntelligentSolution from '@/assets/svg/icon-intelligentSolution.svg';
|
import IconIntelligentSolution from '@/assets/svg/svg-intelligentSolution.svg';
|
||||||
|
import IconProjectManagement from '@/assets/svg/svg-projectManagement.svg';
|
||||||
|
|
||||||
const COMPONENTS: AppRouteRecordRaw[] = [
|
const COMPONENTS: AppRouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
@ -158,13 +159,50 @@ const COMPONENTS: AppRouteRecordRaw[] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// path: '/intelligent-solution',
|
||||||
|
// name: 'IntelligentSolution',
|
||||||
|
// redirect: 'intelligent-solution/businessAnalysisReport',
|
||||||
|
// meta: {
|
||||||
|
// locale: '智能方案管理',
|
||||||
|
// icon: IconIntelligentSolution,
|
||||||
|
// requiresAuth: true,
|
||||||
|
// requireLogin: true,
|
||||||
|
// roles: ['*'],
|
||||||
|
// id: MENU_GROUP_IDS.PROPERTY_ID,
|
||||||
|
// },
|
||||||
|
// children: [
|
||||||
|
// {
|
||||||
|
// path: 'businessAnalysisReport',
|
||||||
|
// name: 'IntelligentSolutionBusinessAnalysisReport',
|
||||||
|
// meta: {
|
||||||
|
// locale: '业务洞察报告',
|
||||||
|
// requiresAuth: true,
|
||||||
|
// requireLogin: true,
|
||||||
|
// roles: ['*'],
|
||||||
|
// },
|
||||||
|
// component: () => import('@/views/property-marketing/intelligent-solution/businessAnalysisReport'),
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: 'competitiveProductAnalysisReport',
|
||||||
|
// name: 'IntelligentSolutionCompetitiveProductAnalysisReport',
|
||||||
|
// meta: {
|
||||||
|
// locale: '竟品对比报告',
|
||||||
|
// requiresAuth: true,
|
||||||
|
// requireLogin: true,
|
||||||
|
// roles: ['*'],
|
||||||
|
// },
|
||||||
|
// component: () => import('@/views/property-marketing/intelligent-solution/competitiveProductAnalysisReport'),
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
path: '/intelligent-solution',
|
path: '/project-manage',
|
||||||
name: 'IntelligentSolution',
|
name: 'ProjectManagement',
|
||||||
redirect: 'intelligent-solution/businessAnalysisReport',
|
redirect: 'project-manage/project-list',
|
||||||
meta: {
|
meta: {
|
||||||
locale: '智能方案管理',
|
locale: '项目管理',
|
||||||
icon: IconIntelligentSolution,
|
icon: IconProjectManagement,
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
requireLogin: true,
|
requireLogin: true,
|
||||||
roles: ['*'],
|
roles: ['*'],
|
||||||
@ -172,26 +210,15 @@ const COMPONENTS: AppRouteRecordRaw[] = [
|
|||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'businessAnalysisReport',
|
path: 'project-list',
|
||||||
name: 'IntelligentSolutionBusinessAnalysisReport',
|
name: 'ProjectList',
|
||||||
meta: {
|
meta: {
|
||||||
locale: '业务洞察报告',
|
locale: '项目列表',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
requireLogin: true,
|
requireLogin: true,
|
||||||
roles: ['*'],
|
roles: ['*'],
|
||||||
},
|
},
|
||||||
component: () => import('@/views/property-marketing/intelligent-solution/businessAnalysisReport'),
|
component: () => import('@/views/property-marketing/project-manage/project-list'),
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'competitiveProductAnalysisReport',
|
|
||||||
name: 'IntelligentSolutionCompetitiveProductAnalysisReport',
|
|
||||||
meta: {
|
|
||||||
locale: '竟品对比报告',
|
|
||||||
requiresAuth: true,
|
|
||||||
requireLogin: true,
|
|
||||||
roles: ['*'],
|
|
||||||
},
|
|
||||||
component: () => import('@/views/property-marketing/intelligent-solution/competitiveProductAnalysisReport'),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -77,12 +77,19 @@ export const MENU_LIST = [
|
|||||||
'guideDetail',
|
'guideDetail',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// name: '智能方案管理',
|
||||||
|
// routeName: 'IntelligentSolutionBusinessAnalysisReport',
|
||||||
|
// includeRouteNames: [
|
||||||
|
// 'IntelligentSolutionBusinessAnalysisReport',
|
||||||
|
// 'IntelligentSolutionCompetitiveProductAnalysisReport',
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
name: '智能方案管理',
|
name: '项目管理',
|
||||||
routeName: 'IntelligentSolutionBusinessAnalysisReport',
|
routeName: 'ProjectList',
|
||||||
includeRouteNames: [
|
includeRouteNames: [
|
||||||
'IntelligentSolutionBusinessAnalysisReport',
|
'ProjectList',
|
||||||
'IntelligentSolutionCompetitiveProductAnalysisReport',
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -7,18 +7,22 @@ import router from '@/router';
|
|||||||
import type { RouteLocationNormalized } from 'vue-router';
|
import type { RouteLocationNormalized } from 'vue-router';
|
||||||
import { MENU_LIST } from './constants';
|
import { MENU_LIST } from './constants';
|
||||||
import { useEnterpriseStore } from '@/stores/modules/enterprise';
|
import { useEnterpriseStore } from '@/stores/modules/enterprise';
|
||||||
|
import { getTaskUnread, patchTaskRead } from '@/api/all/common';
|
||||||
|
|
||||||
interface sidebarState {
|
interface sidebarState {
|
||||||
activeMenuId: number | null;
|
activeMenuId: number | null;
|
||||||
menuList: any[];
|
menuList: any[];
|
||||||
allowAccessRoutes: any[];
|
allowAccessRoutes: any[];
|
||||||
|
unreadInfo: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let unreadInfoTimer: number | null = null;
|
||||||
|
|
||||||
export const useSidebarStore = defineStore('sidebar', {
|
export const useSidebarStore = defineStore('sidebar', {
|
||||||
state: (): sidebarState => ({
|
state: (): sidebarState => ({
|
||||||
activeMenuId: null,
|
activeMenuId: null, //
|
||||||
menuList: [],
|
menuList: [], // 菜单信息
|
||||||
|
unreadInfo: [], // 未读消息
|
||||||
allowAccessRoutes: [], // 允许访问的路由列表
|
allowAccessRoutes: [], // 允许访问的路由列表
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
@ -42,17 +46,13 @@ export const useSidebarStore = defineStore('sidebar', {
|
|||||||
setActiveMenuIdByRoute(route: RouteLocationNormalized) {
|
setActiveMenuIdByRoute(route: RouteLocationNormalized) {
|
||||||
const appRoutes = router.options?.routes ?? [];
|
const appRoutes = router.options?.routes ?? [];
|
||||||
|
|
||||||
// 查找当前路由所属的菜单组
|
|
||||||
const findMenuGroup = (routes: any[]): number | null => {
|
const findMenuGroup = (routes: any[]): number | null => {
|
||||||
for (const routeItem of routes) {
|
for (const routeItem of routes) {
|
||||||
// 检查子路由
|
|
||||||
if (routeItem.children?.length > 0) {
|
if (routeItem.children?.length > 0) {
|
||||||
// 检查当前路由是否是这个父路由的子路由
|
|
||||||
const isChildRoute = routeItem.children.some((child: any) => child.name === route.name);
|
const isChildRoute = routeItem.children.some((child: any) => child.name === route.name);
|
||||||
if (isChildRoute) {
|
if (isChildRoute) {
|
||||||
return routeItem.meta?.id || null;
|
return routeItem.meta?.id || null;
|
||||||
}
|
}
|
||||||
// 递归检查更深层的子路由
|
|
||||||
const childResult = findMenuGroup(routeItem.children);
|
const childResult = findMenuGroup(routeItem.children);
|
||||||
if (childResult !== null) {
|
if (childResult !== null) {
|
||||||
return routeItem.meta?.id || childResult;
|
return routeItem.meta?.id || childResult;
|
||||||
@ -71,5 +71,31 @@ export const useSidebarStore = defineStore('sidebar', {
|
|||||||
this.activeMenuId = menuId;
|
this.activeMenuId = menuId;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async getTaskUnreadInfo() {
|
||||||
|
const { code, data } = await getTaskUnread();
|
||||||
|
if (code === 200) {
|
||||||
|
this.unreadInfo = data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 查询未读信息
|
||||||
|
startUnreadInfoPolling() {
|
||||||
|
if (unreadInfoTimer) return;
|
||||||
|
this.getTaskUnreadInfo();
|
||||||
|
unreadInfoTimer = window.setInterval(() => {
|
||||||
|
this.getTaskUnreadInfo();
|
||||||
|
}, 30000);
|
||||||
|
},
|
||||||
|
stopUnreadInfoPolling() {
|
||||||
|
this.unreadInfo = [];
|
||||||
|
if (unreadInfoTimer) {
|
||||||
|
clearInterval(unreadInfoTimer);
|
||||||
|
unreadInfoTimer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async removeTaskUnreadInfo() {
|
||||||
|
patchTaskRead({ ids: this.unreadInfo });
|
||||||
|
this.unreadInfo = [];
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -47,7 +47,7 @@ export const useUserStore = defineStore('user', {
|
|||||||
slsWithCatch('accessToken', this.token);
|
slsWithCatch('accessToken', this.token);
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteToken() {
|
clearToken() {
|
||||||
this.token = '';
|
this.token = '';
|
||||||
rlsWithCatch('accessToken');
|
rlsWithCatch('accessToken');
|
||||||
},
|
},
|
||||||
|
|||||||
183
src/styles/components/button.scss
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
.arco-btn {
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid #d7d7d9 !important;
|
||||||
|
color: #3c4043 !important;
|
||||||
|
font-family: $font-family-regular;
|
||||||
|
font-size: 14px !important;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400 !important;
|
||||||
|
line-height: 22px !important;
|
||||||
|
&.arco-btn-disabled {
|
||||||
|
border-color: #f2f3f5 !important;
|
||||||
|
color: #b1b2b5 !important;
|
||||||
|
}
|
||||||
|
&:not(.arco-btn-disabled) {
|
||||||
|
&:hover {
|
||||||
|
border-color: #e6e6e8 !important;
|
||||||
|
color: #737478 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-btn-primary {
|
||||||
|
background-color: $color-primary !important;
|
||||||
|
border: none !important;
|
||||||
|
color: #fff !important;
|
||||||
|
&.arco-btn-disabled {
|
||||||
|
color: #fff !important;
|
||||||
|
background-color: $color-primary-3 !important;
|
||||||
|
}
|
||||||
|
&:not(.arco-btn-disabled) {
|
||||||
|
&:hover {
|
||||||
|
color: #fff !important;
|
||||||
|
background-color: $color-primary-5 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//success
|
||||||
|
&.arco-btn-status-success {
|
||||||
|
background-color: $color-success !important;
|
||||||
|
&.arco-btn-disabled {
|
||||||
|
background-color: $color-success-3 !important;
|
||||||
|
}
|
||||||
|
&:not(.arco-btn-disabled) {
|
||||||
|
&:hover {
|
||||||
|
background-color: $color-success-5 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// danger
|
||||||
|
&.arco-btn-status-danger {
|
||||||
|
background-color: $color-error !important;
|
||||||
|
&.arco-btn-disabled {
|
||||||
|
background-color: $color-error-3 !important;
|
||||||
|
}
|
||||||
|
&:not(.arco-btn-disabled) {
|
||||||
|
&:hover {
|
||||||
|
background-color: $color-error-5 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// warning
|
||||||
|
&.arco-btn-status-warning {
|
||||||
|
background-color: $color-warning !important;
|
||||||
|
&.arco-btn-disabled {
|
||||||
|
background-color: $color-warning-3 !important;
|
||||||
|
}
|
||||||
|
&:not(.arco-btn-disabled) {
|
||||||
|
&:hover {
|
||||||
|
background-color: $color-warning-5 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-btn-outline {
|
||||||
|
border: 1px solid $color-primary !important;
|
||||||
|
color: $color-primary !important;
|
||||||
|
&.arco-btn-disabled {
|
||||||
|
border-color: $color-primary-3 !important;
|
||||||
|
color: $color-primary-3 !important;
|
||||||
|
}
|
||||||
|
&:not(.arco-btn-disabled) {
|
||||||
|
&:hover {
|
||||||
|
border-color: $color-primary-5 !important;
|
||||||
|
color: $color-primary-5 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.arco-btn-status-success {
|
||||||
|
border: 1px solid $color-success !important;
|
||||||
|
color: $color-success !important;
|
||||||
|
&.arco-btn-disabled {
|
||||||
|
border-color: $color-success-3 !important;
|
||||||
|
color: $color-success-3 !important;
|
||||||
|
}
|
||||||
|
&:not(.arco-btn-disabled) {
|
||||||
|
&:hover {
|
||||||
|
border-color: $color-success-5 !important;
|
||||||
|
color: $color-success-5 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.arco-btn-status-danger {
|
||||||
|
border: 1px solid $color-error !important;
|
||||||
|
color: $color-error !important;
|
||||||
|
&.arco-btn-disabled {
|
||||||
|
border-color: $color-error-3 !important;
|
||||||
|
color: $color-error-3 !important;
|
||||||
|
}
|
||||||
|
&:not(.arco-btn-disabled) {
|
||||||
|
&:hover {
|
||||||
|
border-color: $color-error-5 !important;
|
||||||
|
color: $color-error-5 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.arco-btn-status-warning {
|
||||||
|
border: 1px solid $color-warning !important;
|
||||||
|
color: $color-warning !important;
|
||||||
|
&.arco-btn-disabled {
|
||||||
|
border-color: $color-warning-3 !important;
|
||||||
|
color: $color-warning-3 !important;
|
||||||
|
}
|
||||||
|
&:not(.arco-btn-disabled) {
|
||||||
|
&:hover {
|
||||||
|
border-color: $color-warning-5 !important;
|
||||||
|
color: $color-warning-5 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-btn-text {
|
||||||
|
background-color: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
color: $color-primary !important;
|
||||||
|
&.arco-btn-disabled {
|
||||||
|
color: $color-primary-2 !important;
|
||||||
|
}
|
||||||
|
&:not(.arco-btn-disabled) {
|
||||||
|
&:hover {
|
||||||
|
color: $color-primary-5 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.arco-btn-status-success {
|
||||||
|
color: $color-success !important;
|
||||||
|
&.arco-btn-disabled {
|
||||||
|
color: $color-success-2 !important;
|
||||||
|
}
|
||||||
|
&:not(.arco-btn-disabled) {
|
||||||
|
&:hover {
|
||||||
|
color: $color-success-5 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.arco-btn-status-danger {
|
||||||
|
color: $color-error !important;
|
||||||
|
&.arco-btn-disabled {
|
||||||
|
color: $color-error-2 !important;
|
||||||
|
}
|
||||||
|
&:not(.arco-btn-disabled) {
|
||||||
|
&:hover {
|
||||||
|
color: $color-error-5 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.arco-btn-status-warning {
|
||||||
|
color: $color-warning !important;
|
||||||
|
&.arco-btn-disabled {
|
||||||
|
color: $color-warning-2 !important;
|
||||||
|
}
|
||||||
|
&:not(.arco-btn-disabled) {
|
||||||
|
&:hover {
|
||||||
|
color: $color-warning-5 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,7 +24,8 @@
|
|||||||
&.arco-checkbox-checked,
|
&.arco-checkbox-checked,
|
||||||
&.arco-checkbox-indeterminate {
|
&.arco-checkbox-indeterminate {
|
||||||
.arco-checkbox-icon {
|
.arco-checkbox-icon {
|
||||||
background-color: #6d4cfe !important;
|
background-color: #6D4CFE !important;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/styles/components/date-picker.scss
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
.arco-picker {
|
||||||
|
border-radius: 4px !important;
|
||||||
|
border-color: #d7d7d9 !important;
|
||||||
|
background-color: #fff !important;
|
||||||
|
&:hover {
|
||||||
|
background-color: #fff !important;
|
||||||
|
}
|
||||||
|
&:focus-within,
|
||||||
|
&.arco-input-focus,
|
||||||
|
&.arco-textarea-focus {
|
||||||
|
background-color: var(--color-bg-2) !important;
|
||||||
|
border-color: rgb(var(--primary-6)) !important;
|
||||||
|
box-shadow: 0 0 0 0 var(--color-primary-light-2) !important;
|
||||||
|
}
|
||||||
|
&.arco-picker-disabled {
|
||||||
|
background-color: var(--BG-200, #F2F3F5) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/styles/components/form.scss
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
.arco-form {
|
||||||
|
.arco-form-item {
|
||||||
|
margin-bottom: 16px !important;
|
||||||
|
.arco-form-item-label-col {
|
||||||
|
padding-right: 12px !important;
|
||||||
|
.arco-form-item-label {
|
||||||
|
color: #211f24;
|
||||||
|
font-family: $font-family-regular;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
.arco-form-item-label-required-symbol {
|
||||||
|
color: #f64b31;
|
||||||
|
margin-right: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: $font-family-regular;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,3 +4,9 @@
|
|||||||
@import './pagination.scss';
|
@import './pagination.scss';
|
||||||
@import './tabs.scss';
|
@import './tabs.scss';
|
||||||
@import './modal.scss';
|
@import './modal.scss';
|
||||||
|
@import "./textarea.scss";
|
||||||
|
@import "./select.scss";
|
||||||
|
@import "./date-picker.scss";
|
||||||
|
@import "./button.scss";
|
||||||
|
@import "./steps.scss";
|
||||||
|
@import "./form.scss";
|
||||||
@ -1,5 +1,18 @@
|
|||||||
.arco-input-wrapper {
|
.arco-input-wrapper {
|
||||||
border-radius: 4px;
|
border-radius: 4px !important;
|
||||||
border-color: #d7d7d9;
|
border-color: #d7d7d9 !important;
|
||||||
background-color: #fff;
|
background-color: #fff !important;
|
||||||
|
&:hover {
|
||||||
|
background-color: #fff !important;
|
||||||
|
}
|
||||||
|
&:focus-within,
|
||||||
|
&.arco-input-focus,
|
||||||
|
&.arco-textarea-focus {
|
||||||
|
background-color: var(--color-bg-2) !important;
|
||||||
|
border-color: rgb(var(--primary-6)) !important;
|
||||||
|
box-shadow: 0 0 0 0 var(--color-primary-light-2) !important;
|
||||||
|
}
|
||||||
|
&.arco-input-disabled {
|
||||||
|
background-color: var(--BG-200, #F2F3F5) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,11 @@
|
|||||||
height: 56px;
|
height: 56px;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
.arco-modal-title {
|
.arco-modal-title {
|
||||||
|
font-family: $font-family-medium;
|
||||||
|
color: #211f24;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/styles/components/select.scss
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
.arco-select {
|
||||||
|
border-radius: 4px !important;
|
||||||
|
border-color: #d7d7d9 !important;
|
||||||
|
background-color: #fff !important;
|
||||||
|
&:hover {
|
||||||
|
background-color: #fff !important;
|
||||||
|
}
|
||||||
|
&:focus-within,
|
||||||
|
&.arco-input-focus,
|
||||||
|
&.arco-textarea-focus {
|
||||||
|
background-color: var(--color-bg-2) !important;
|
||||||
|
border-color: rgb(var(--primary-6)) !important;
|
||||||
|
box-shadow: 0 0 0 0 var(--color-primary-light-2) !important;
|
||||||
|
}
|
||||||
|
&.arco-select-view-disabled {
|
||||||
|
background-color: var(--BG-200, #F2F3F5) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/styles/components/steps.scss
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
.arco-steps {
|
||||||
|
.arco-steps-item {
|
||||||
|
.arco-steps-item-node {
|
||||||
|
.arco-steps-icon {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 32px;
|
||||||
|
color: #3c4043;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 24px;
|
||||||
|
font-family: $font-family-manrope-medium;
|
||||||
|
background-color: #f2f3f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.arco-steps-item-content {
|
||||||
|
.arco-steps-item-title {
|
||||||
|
color: #3c4043;
|
||||||
|
font-family: $font-family-regular;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
&::after {
|
||||||
|
background-color: #e6e6e8 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-active,
|
||||||
|
&-finish {
|
||||||
|
.arco-steps-item-node {
|
||||||
|
.arco-steps-icon {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #6d4cfe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.arco-steps-item-content {
|
||||||
|
.arco-steps-item-title {
|
||||||
|
font-family: $font-family-medium;
|
||||||
|
&::after {
|
||||||
|
background-color: #6d4cfe !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/styles/components/textarea.scss
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
.arco-textarea-wrapper {
|
||||||
|
border-radius: 4px !important;
|
||||||
|
border-color: #d7d7d9 !important;
|
||||||
|
background-color: #fff !important;
|
||||||
|
&:hover {
|
||||||
|
background-color: #fff !important;
|
||||||
|
}
|
||||||
|
&:focus-within,
|
||||||
|
&.arco-input-focus,
|
||||||
|
&.arco-textarea-focus {
|
||||||
|
background-color: var(--color-bg-2) !important;
|
||||||
|
border-color: rgb(var(--primary-6)) !important;
|
||||||
|
box-shadow: 0 0 0 0 var(--color-primary-light-2) !important;
|
||||||
|
}
|
||||||
|
&.arco-textarea-disabled {
|
||||||
|
background-color: var(--BG-200, #F2F3F5) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,9 +2,9 @@
|
|||||||
* @Author: RenXiaoDong
|
* @Author: RenXiaoDong
|
||||||
* @Date: 2025-06-30 16:03:42
|
* @Date: 2025-06-30 16:03:42
|
||||||
*/
|
*/
|
||||||
|
@import './font.scss';
|
||||||
@import './lib/variable.scss';
|
@import './lib/variable.scss';
|
||||||
@import './lib/reset.scss';
|
@import './lib/reset.scss';
|
||||||
|
|
||||||
@import './vars.scss';
|
@import './vars.scss';
|
||||||
@import './components/index.scss';
|
@import './components/index.scss';
|
||||||
// @import './font.scss';
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ html,
|
|||||||
body {
|
body {
|
||||||
background: $color-background;
|
background: $color-background;
|
||||||
font-family: $font-family-regular;
|
font-family: $font-family-regular;
|
||||||
font-size: $font-size-14;
|
font-size: 14px;
|
||||||
-webkit-print-color-adjust: exact;
|
-webkit-print-color-adjust: exact;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ $font-family-regular: 'PingFangSC-Regular', 'Microsoft Yahei', Arial, sans-serif
|
|||||||
$font-family-medium: 'PingFangSC-Medium', 'Microsoft Yahei', Arial, sans-serif;
|
$font-family-medium: 'PingFangSC-Medium', 'Microsoft Yahei', Arial, sans-serif;
|
||||||
$font-family-light: 'PingFangSC-Light', 'Microsoft Yahei', Arial, sans-serif;
|
$font-family-light: 'PingFangSC-Light', 'Microsoft Yahei', Arial, sans-serif;
|
||||||
$font-family-bold: 'PingFangSC-Semibold', 'PingFangSC-Regular', 'Microsoft Yahei', Arial, sans-serif;
|
$font-family-bold: 'PingFangSC-Semibold', 'PingFangSC-Regular', 'Microsoft Yahei', Arial, sans-serif;
|
||||||
|
|
||||||
// 数字字体包
|
// 数字字体包
|
||||||
$font-family-manrope-regular: 'Manrope-Regular';
|
$font-family-manrope-regular: 'Manrope-Regular';
|
||||||
$font-family-manrope-medium: 'Manrope-Medium';
|
$font-family-manrope-medium: 'Manrope-Medium';
|
||||||
@ -12,4 +13,44 @@ $font-family-manrope-semiBold: 'Manrope-SemiBold';
|
|||||||
|
|
||||||
$color-background: #f9f9f9;
|
$color-background: #f9f9f9;
|
||||||
|
|
||||||
$font-size-14: 14px;
|
$color-primary: #6d4cfe; // 常规
|
||||||
|
$color-primary-5: #8A70FE; // hover
|
||||||
|
$color-primary-7: #573DCB; // click
|
||||||
|
$color-primary-3: #A794FE; // disabled
|
||||||
|
$color-primary-2: #C5B7FF; // text disabled
|
||||||
|
$color-primary-1: #F0EDFF; // 浅色
|
||||||
|
|
||||||
|
$color-success: #25C883;
|
||||||
|
$color-success-5: #57CF9C;
|
||||||
|
$color-success-7: #1BAE71;
|
||||||
|
$color-success-3: #81DBB5;
|
||||||
|
$color-success-2: #ABE7CE;
|
||||||
|
$color-success-1: #EBF7F2;
|
||||||
|
|
||||||
|
$color-warning: #FFAE00;
|
||||||
|
$color-warning-5: #FFBE33;
|
||||||
|
$color-warning-7: #CC8B00;
|
||||||
|
$color-warning-3: #FFCF66;
|
||||||
|
$color-warning-2: #FFDF99;
|
||||||
|
$color-warning-1: #FFF7E5;
|
||||||
|
|
||||||
|
$color-error: #F64B31;
|
||||||
|
$color-error-5: #F86F5A;
|
||||||
|
$color-error-7: #C53C27;
|
||||||
|
$color-error-3: #FA9383;
|
||||||
|
$color-error-2: #FBB7AD;
|
||||||
|
$color-error-1: #FFE9E7;
|
||||||
|
|
||||||
|
$color-blue: #2A59F3;
|
||||||
|
$color-blue-5: #557AF6;
|
||||||
|
$color-blue-7: #2247C2;
|
||||||
|
$color-blue-3: #7F9CF8;
|
||||||
|
$color-blue-2: #AABDFA;
|
||||||
|
$color-blue-1: #E5ECFF;
|
||||||
|
|
||||||
|
$color-teal: #39C6E9;
|
||||||
|
$color-teal-5: #60D2ED;
|
||||||
|
$color-teal-7: #2E9EBA;
|
||||||
|
$color-teal-3: #88DDF2;
|
||||||
|
$color-teal-2: #B0E8F6;
|
||||||
|
$color-teal-1: #E1F9FF;
|
||||||
14
src/styles/mixins/ellipsis.scss
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
@mixin multi-ellipsis($lines) {
|
||||||
|
display: -webkit-box;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
-webkit-line-clamp: $lines;
|
||||||
|
/* autoprefixer: ignore next */
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin ellipsis {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
1
src/styles/mixins/index.scss
Normal file
@ -0,0 +1 @@
|
|||||||
|
@import "./ellipsis.scss"
|
||||||
94
src/utils/arcoD.tsx
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { Notification } from '@arco-design/web-vue';
|
||||||
|
import { downloadByUrl } from '@/utils/tools';
|
||||||
|
import { IconLoading } from '@arco-design/web-vue/es/icon';
|
||||||
|
|
||||||
|
|
||||||
|
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
|
||||||
|
import icon2 from '@/assets/img/media-account/icon-success.png';
|
||||||
|
import icon3 from "@/assets/img/media-account/icon-warn.png"
|
||||||
|
|
||||||
|
interface RenderNotificationData {
|
||||||
|
total_number: number;
|
||||||
|
success_number: number;
|
||||||
|
fail_number: number;
|
||||||
|
file?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载通知框
|
||||||
|
export function showExportNotification(label: string, others: { id?: string, duration?: number }) {
|
||||||
|
const { id = '', duration = 3000 } = others ?? {}
|
||||||
|
Notification.warning({
|
||||||
|
id,
|
||||||
|
showIcon: false,
|
||||||
|
closable: true,
|
||||||
|
content: () => (
|
||||||
|
<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',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载失败框
|
||||||
|
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: () => (
|
||||||
|
<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" />
|
||||||
|
<p class="text-14px lh-22px font-400 color-#211F24 ">{label}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-14px lh-22px font-400 color-#6D4CFE cursor-pointer" onClick={() => onReDownload?.()}>重新下载</p>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
duration,
|
||||||
|
class: 'px-16px py-9px w-500px rounded-2px bg-#FFE9E7',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const showImportResultNotification = (data: RenderNotificationData) => {
|
||||||
|
const { total_number, success_number, fail_number, file } = data;
|
||||||
|
const hasError = fail_number > 0;
|
||||||
|
const handleDownloadError = (file?: string) => {
|
||||||
|
file && downloadByUrl(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
Notification.warning({
|
||||||
|
showIcon: false,
|
||||||
|
closable: true,
|
||||||
|
content: () => (
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center mb-4px">
|
||||||
|
<img src={hasError ? icon1 : icon2} width="16" height="16" class="mr-8px" />
|
||||||
|
<span class="text-16px lh-24px font-400 color-#211F24">导入完成</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-14px lh-22px font-400 color-#211F24">
|
||||||
|
共导入 {total_number} 个账号,导入成功 {success_number} 个
|
||||||
|
{hasError && (
|
||||||
|
<span>
|
||||||
|
,失败 <span class="color-#F64B31">{fail_number}</span> 个
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
{hasError && (
|
||||||
|
<div
|
||||||
|
class="mt-8px text-14px lh-22px font-400 color-#6D4CFE cursor-pointer"
|
||||||
|
onClick={() => handleDownloadError(file)}
|
||||||
|
>
|
||||||
|
下载问题表格
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
duration: 3000,
|
||||||
|
class: `px-16px py-16px w-400px rounded-2px ${hasError ? 'bg-#FFF7E5' : 'bg-#EBF7F2'}`,
|
||||||
|
});
|
||||||
|
};
|
||||||
55
src/utils/platform.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import icon1 from '@/assets/img/media-account/icon-jl.png';
|
||||||
|
import icon2 from '@/assets/img/media-account/icon-jg.png';
|
||||||
|
import icon3 from '@/assets/img/media-account/icon-bili.png';
|
||||||
|
import icon4 from '@/assets/img/media-account/icon-dy.png';
|
||||||
|
import icon5 from '@/assets/img/media-account/icon-xhs.png';
|
||||||
|
|
||||||
|
// 投放账户
|
||||||
|
export enum ENUM_PUT_ACCOUNT_PLATFORM {
|
||||||
|
jl = 0,
|
||||||
|
jg = 1,
|
||||||
|
bili = 2,
|
||||||
|
};
|
||||||
|
// 新媒体账号
|
||||||
|
export enum ENUM_MEDIA_ACCOUNT_PLATFORM {
|
||||||
|
dy = 0,
|
||||||
|
xhs = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PLATFORM_LIST = [
|
||||||
|
{
|
||||||
|
label: '巨量',
|
||||||
|
value: ENUM_PUT_ACCOUNT_PLATFORM.jl,
|
||||||
|
icon: icon1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '聚光',
|
||||||
|
value: ENUM_PUT_ACCOUNT_PLATFORM.jg,
|
||||||
|
icon: icon2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'B站',
|
||||||
|
value: ENUM_PUT_ACCOUNT_PLATFORM.bili,
|
||||||
|
icon: icon3,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const MEDIA_ACCOUNT_PLATFORMS = [
|
||||||
|
{
|
||||||
|
label: '抖音',
|
||||||
|
value: 0,
|
||||||
|
icon: icon4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '小红书',
|
||||||
|
value: 1,
|
||||||
|
icon: icon5,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getPutAccountPlatformLogo = (value: ENUM_PUT_ACCOUNT_PLATFORM) => {
|
||||||
|
return PLATFORM_LIST.find((v) => v.value === value)?.icon ?? null;
|
||||||
|
};
|
||||||
|
export const getMediaAccountPlatformLogo = (value: ENUM_MEDIA_ACCOUNT_PLATFORM) => {
|
||||||
|
return MEDIA_ACCOUNT_PLATFORMS.find((v) => v.value === value)?.icon ?? null;
|
||||||
|
};
|
||||||
@ -3,6 +3,7 @@
|
|||||||
* @Date: 2025-06-27 17:36:31
|
* @Date: 2025-06-27 17:36:31
|
||||||
*/
|
*/
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
export function toFixed(num: number | string, n: number): number {
|
export function toFixed(num: number | string, n: number): number {
|
||||||
return parseFloat(parseFloat(num.toString()).toFixed(n));
|
return parseFloat(parseFloat(num.toString()).toFixed(n));
|
||||||
}
|
}
|
||||||
@ -84,7 +85,7 @@ export function formatTableField(fieldItem: any, rowValue: any, showExactValue =
|
|||||||
return `${fieldItem.prefix || ''}${value}${fieldItem.suffix || ''}`;
|
return `${fieldItem.prefix || ''}${value}${fieldItem.suffix || ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function exactFormatTime(val: number, curYearFmt = 'MM-DD HH:mm', otherYearFmt = 'YYYY-MM-DD HH:mm') {
|
export function exactFormatTime(val: number, curYearFmt = 'MM-DD HH:mm:ss', otherYearFmt = 'YYYY-MM-DD HH:mm:ss') {
|
||||||
if (!val) return '-';
|
if (!val) return '-';
|
||||||
const year = dayjs(val * 1000).year();
|
const year = dayjs(val * 1000).year();
|
||||||
const currYear = dayjs().year();
|
const currYear = dayjs().year();
|
||||||
@ -105,3 +106,7 @@ export function downloadByUrl(url: string, filename?: string) {
|
|||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function genRandomId() {
|
||||||
|
return `id_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
|
||||||
|
}
|
||||||
@ -12,13 +12,13 @@ import { useSidebarStore } from '@/stores/modules/side-bar';
|
|||||||
export function goUserLogin(query?: any) {
|
export function goUserLogin(query?: any) {
|
||||||
router.push({ name: 'UserLogin', query });
|
router.push({ name: 'UserLogin', query });
|
||||||
}
|
}
|
||||||
// 初始化企业信息、navbar菜单、允许访问的路由
|
|
||||||
export const getUserEnterpriseInfo = async () => {
|
export const getUserEnterpriseInfo = async () => {
|
||||||
const enterpriseStore = useEnterpriseStore();
|
const enterpriseStore = useEnterpriseStore();
|
||||||
const sidebarStore = useSidebarStore();
|
const sidebarStore = useSidebarStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
await enterpriseStore.getEnterpriseInfo();
|
await enterpriseStore.getEnterpriseInfo(); // 初始化企业信息
|
||||||
sidebarStore.getUserNavbarMenuList(); // 初始化navbar菜单
|
sidebarStore.getUserNavbarMenuList(); // 初始化navbar菜单
|
||||||
userStore.getUserAllowAccessRoutes(); // 初始化允许访问的路由
|
userStore.getUserAllowAccessRoutes(); // 初始化允许访问的路由
|
||||||
};
|
};
|
||||||
@ -26,9 +26,11 @@ export const getUserEnterpriseInfo = async () => {
|
|||||||
// 登录处理
|
// 登录处理
|
||||||
export async function handleUserLogin() {
|
export async function handleUserLogin() {
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const sidebarStore = useSidebarStore();
|
||||||
|
|
||||||
await userStore.getUserInfo(); // 初始化用户信息
|
await userStore.getUserInfo(); // 初始化用户信息
|
||||||
await getUserEnterpriseInfo();
|
await getUserEnterpriseInfo(); // 初始化企业信息、navbar菜单、允许访问的路由
|
||||||
|
sidebarStore.startUnreadInfoPolling(); // 初始化未读信息
|
||||||
|
|
||||||
handleUserHome();
|
handleUserHome();
|
||||||
}
|
}
|
||||||
@ -38,18 +40,19 @@ export function handleUserHome() {
|
|||||||
router.push({ name: 'Home' });
|
router.push({ name: 'Home' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 登出处理
|
||||||
export function handleUserLogout() {
|
export function handleUserLogout() {
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const enterpriseStore = useEnterpriseStore();
|
const enterpriseStore = useEnterpriseStore();
|
||||||
const sidebarStore = useSidebarStore();
|
const sidebarStore = useSidebarStore();
|
||||||
|
|
||||||
userStore.clearUserInfo();
|
userStore.clearUserInfo(); // 清除用户信息
|
||||||
enterpriseStore.clearUserEnterpriseInfo();
|
userStore.clearToken(); // 清除token
|
||||||
sidebarStore.clearUserNavbarMenuList();
|
enterpriseStore.clearUserEnterpriseInfo(); // 清除企业信息
|
||||||
userStore.clearUserAllowAccessRoutes();
|
sidebarStore.clearUserNavbarMenuList(); // 清除navbar菜单信息
|
||||||
|
userStore.clearUserAllowAccessRoutes(); // 清除权限路由列表
|
||||||
sidebarStore.clearActiveMenuId();
|
sidebarStore.stopUnreadInfoPolling(); // 清除未读消息
|
||||||
userStore.deleteToken();
|
sidebarStore.clearActiveMenuId(); // 清除active菜单id
|
||||||
|
|
||||||
goUserLogin();
|
goUserLogin();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -133,7 +133,7 @@
|
|||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<a-button size="large" class="cancel-btn" @click="handleCancel">取消</a-button>
|
<a-button size="large" @click="handleCancel">取消</a-button>
|
||||||
<a-button type="primary" size="large" class="rounded-4px" @click="handleOk"> 确定 </a-button>
|
<a-button type="primary" size="large" class="rounded-4px" @click="handleOk"> 确定 </a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@ -397,14 +397,6 @@ const handleOk = () => {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-top: 1px solid var(--Border-1, #d7d7d9);
|
border-top: 1px solid var(--Border-1, #d7d7d9);
|
||||||
.cancel-btn {
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid var(--BG-500, #b1b2b5);
|
|
||||||
background: var(--BG-white, #fff);
|
|
||||||
&:hover {
|
|
||||||
border: 1px solid var(--BG-500, #b1b2b5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -260,7 +260,7 @@
|
|||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<a-button size="large" class="cancel-btn" @click="handleCancel">取消</a-button>
|
<a-button size="large" @click="handleCancel">取消</a-button>
|
||||||
<a-button type="primary" size="large" class="rounded-4px" @click="handleOk"> 确定 </a-button>
|
<a-button type="primary" size="large" class="rounded-4px" @click="handleOk"> 确定 </a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@ -713,14 +713,6 @@ onMounted(() => {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-top: 1px solid var(--Border-1, #d7d7d9);
|
border-top: 1px solid var(--Border-1, #d7d7d9);
|
||||||
.cancel-btn {
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid var(--BG-500, #b1b2b5);
|
|
||||||
background: var(--BG-white, #fff);
|
|
||||||
&:hover {
|
|
||||||
border: 1px solid var(--BG-500, #b1b2b5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -110,7 +110,7 @@
|
|||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<a-button size="large" class="cancel-btn" @click="handleCancel">取消</a-button>
|
<a-button size="large" @click="handleCancel">取消</a-button>
|
||||||
<a-button type="primary" size="large" class="rounded-4px" @click="handleOk"> 确定 </a-button>
|
<a-button type="primary" size="large" class="rounded-4px" @click="handleOk"> 确定 </a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@ -318,14 +318,6 @@ const search = () => {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-top: 1px solid var(--Border-1, #d7d7d9);
|
border-top: 1px solid var(--Border-1, #d7d7d9);
|
||||||
.cancel-btn {
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid var(--BG-500, #b1b2b5);
|
|
||||||
background: var(--BG-white, #fff);
|
|
||||||
&:hover {
|
|
||||||
border: 1px solid var(--BG-500, #b1b2b5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -18,16 +18,16 @@
|
|||||||
<a-space>
|
<a-space>
|
||||||
<span style="width: 8px; height: 8px; background-color: #f64b31; border-radius: 50%"></span>
|
<span style="width: 8px; height: 8px; background-color: #f64b31; border-radius: 50%"></span>
|
||||||
<span>女性</span>
|
<span>女性</span>
|
||||||
<span v-if="genderData.length > 0" style="width: 40px">{{ genderData[0].rate * 100 }}%</span>
|
<span>{{ (girlData.rate * 100).toFixed(2) }}%</span>
|
||||||
<span>TGI</span>
|
<span>TGI</span>
|
||||||
<span v-if="genderData.length > 0">{{ genderData[0].tgi }}</span>
|
<span>{{ girlData.tgi }}</span>
|
||||||
</a-space>
|
</a-space>
|
||||||
<a-space>
|
<a-space>
|
||||||
<span style="width: 8px; height: 8px; background-color: #2a59f3; border-radius: 50%"></span>
|
<span style="width: 8px; height: 8px; background-color: #2a59f3; border-radius: 50%"></span>
|
||||||
<span>男性</span>
|
<span>男性</span>
|
||||||
<span v-if="genderData.length > 1" style="width: 40px">{{ genderData[1].rate * 100 }}%</span>
|
<span>{{ (boyData.rate * 100).toFixed(2) }}%</span>
|
||||||
<span>TGI</span>
|
<span>TGI</span>
|
||||||
<span v-if="genderData.length > 1">{{ genderData[1].tgi }}</span>
|
<span>{{ boyData.tgi }}</span>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-space>
|
</a-space>
|
||||||
@ -145,10 +145,13 @@ const topHeaderRef = ref();
|
|||||||
const selectedIndustry = computed(() => topHeaderRef.value?.selectedIndustry);
|
const selectedIndustry = computed(() => topHeaderRef.value?.selectedIndustry);
|
||||||
const selectedSubCategory = computed(() => topHeaderRef.value?.selectedSubCategory);
|
const selectedSubCategory = computed(() => topHeaderRef.value?.selectedSubCategory);
|
||||||
const selectedTimePeriod = computed(() => topHeaderRef.value?.selectedTimePeriod);
|
const selectedTimePeriod = computed(() => topHeaderRef.value?.selectedTimePeriod);
|
||||||
|
|
||||||
const genderData = ref([]);
|
const genderData = ref([]);
|
||||||
const genderValueData = ref([]);
|
const genderValueData = ref([]);
|
||||||
const ageValueData = ref([]);
|
const ageValueData = ref([]);
|
||||||
const geoList = ref([]);
|
const geoList = ref([]);
|
||||||
|
const boyData = computed(() => genderData.value.find( v => v.gender === 1) ?? {})
|
||||||
|
const girlData = computed(() => genderData.value.find( v => v.gender === 2) ?? {})
|
||||||
// 监听筛选条件变化
|
// 监听筛选条件变化
|
||||||
watch([selectedIndustry, selectedTimePeriod, selectedSubCategory], () => {
|
watch([selectedIndustry, selectedTimePeriod, selectedSubCategory], () => {
|
||||||
getAgeDistributionsList();
|
getAgeDistributionsList();
|
||||||
@ -233,7 +236,7 @@ const getGenderDistributionsList = async () => {
|
|||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
genderValueData.value = data.map((item) => ({
|
genderValueData.value = data.map((item) => ({
|
||||||
value: item.rate * 100,
|
value: (item.rate * 100).toFixed(2),
|
||||||
tgi: item.tgi,
|
tgi: item.tgi,
|
||||||
name: item.gender === 1 ? '女性' : '男性',
|
name: item.gender === 1 ? '女性' : '男性',
|
||||||
}));
|
}));
|
||||||
|
|||||||
@ -54,16 +54,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item hide-label class="mt-68px mb-16px">
|
<a-form-item hide-label class="mt-68px mb-16px">
|
||||||
<div
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
class="w-480 h-48 text-16px rounded-8px text-center text-white leading-48px"
|
class="w-480 h-48 !text-16px !rounded-8px"
|
||||||
:class="disabledSubmitBtn ? 'cursor-no-drop' : 'cursor-pointer'"
|
:class="disabledSubmitBtn ? 'cursor-no-drop' : 'cursor-pointer'"
|
||||||
:style="{ backgroundColor: disabledSubmitBtn ? '#C5B7FF' : '#6D4CFE' }"
|
|
||||||
:disabled="disabledSubmitBtn"
|
:disabled="disabledSubmitBtn"
|
||||||
@click="handleSubmit"
|
@click="handleSubmit"
|
||||||
>
|
>
|
||||||
{{ isLogin ? '登录' : '注册并开通企业账号' }}
|
{{ isLogin ? '登录' : '注册并开通企业账号' }}
|
||||||
</div>
|
</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-space class="text-12px color-#737478 justify-start items-center">
|
<a-space class="text-12px color-#737478 justify-start items-center">
|
||||||
@ -126,13 +125,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import PuzzleVerification from './components/PuzzleVerification.vue';
|
import PuzzleVerification from './components/PuzzleVerification.vue';
|
||||||
import { fetchLoginCaptCha, fetchAuthorizationsCaptcha, fetchProfileInfo } from '@/api/all/login';
|
import { fetchLoginCaptCha, fetchAuthorizationsCaptcha, fetchProfileInfo } from '@/api/all/login';
|
||||||
|
import { joinEnterpriseByInviteCode } from '@/api/all';
|
||||||
import { ref, reactive, onUnmounted, computed } from 'vue';
|
import { ref, reactive, onUnmounted, computed } from 'vue';
|
||||||
import { useUserStore } from '@/stores';
|
import { useUserStore } from '@/stores';
|
||||||
import { useEnterpriseStore } from '@/stores/modules/enterprise';
|
import { useEnterpriseStore } from '@/stores/modules/enterprise';
|
||||||
import { handleUserLogin } from '@/utils/user';
|
import { handleUserLogin } from '@/utils/user';
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
|
const route = useRoute();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const enterpriseStore = useEnterpriseStore();
|
const enterpriseStore = useEnterpriseStore();
|
||||||
const countdown = ref(0);
|
const countdown = ref(0);
|
||||||
@ -145,7 +147,6 @@ const submitting = ref(false);
|
|||||||
const hasCheck = ref(false);
|
const hasCheck = ref(false);
|
||||||
const mobileNumber = ref('');
|
const mobileNumber = ref('');
|
||||||
const selectedAccountIndex = ref(0);
|
const selectedAccountIndex = ref(0);
|
||||||
|
|
||||||
const accounts = ref([]);
|
const accounts = ref([]);
|
||||||
|
|
||||||
const loginForm = reactive({
|
const loginForm = reactive({
|
||||||
@ -267,9 +268,9 @@ const getProfileInfo = async () => {
|
|||||||
let enterprises = data['enterprises'];
|
let enterprises = data['enterprises'];
|
||||||
mobileNumber.value = data['mobile'];
|
mobileNumber.value = data['mobile'];
|
||||||
accounts.value = enterprises;
|
accounts.value = enterprises;
|
||||||
enterpriseStore.setEnterpriseInfo(data);
|
|
||||||
|
|
||||||
if (enterprises.length > 0) {
|
if (enterprises.length > 0) {
|
||||||
|
enterpriseStore.setEnterpriseInfo(data.enterprises[0]);
|
||||||
if (enterprises.length === 1) {
|
if (enterprises.length === 1) {
|
||||||
handleUserLogin();
|
handleUserLogin();
|
||||||
} else {
|
} else {
|
||||||
@ -300,6 +301,15 @@ const handleSubmit = async () => {
|
|||||||
// 处理登录成功逻辑
|
// 处理登录成功逻辑
|
||||||
AMessage.success(isLogin.value ? '登录成功' : '注册成功');
|
AMessage.success(isLogin.value ? '登录成功' : '注册成功');
|
||||||
userStore.setToken(data.access_token);
|
userStore.setToken(data.access_token);
|
||||||
|
|
||||||
|
const { invite_code } = route.query;
|
||||||
|
if (invite_code) {
|
||||||
|
const { code } = await joinEnterpriseByInviteCode(invite_code as string);
|
||||||
|
if (code === 200) {
|
||||||
|
AMessage.success('加入企业成功');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getProfileInfo();
|
getProfileInfo();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<a-table
|
<a-table
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data="data"
|
:data="dataSource"
|
||||||
:pagination="pagination"
|
:pagination="pagination"
|
||||||
class="mt-8px h-540px"
|
class="mt-8px h-540px"
|
||||||
@page-change="handlePageChange"
|
@page-change="handlePageChange"
|
||||||
@ -79,7 +79,7 @@ const columns = [
|
|||||||
slotName: 'action',
|
slotName: 'action',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const data = ref([]);
|
const dataSource = ref([]);
|
||||||
const pagination = reactive({
|
const pagination = reactive({
|
||||||
total: 0,
|
total: 0,
|
||||||
showPageSize: true,
|
showPageSize: true,
|
||||||
@ -99,7 +99,9 @@ const addAccountVisible = ref(false);
|
|||||||
const deleteVisible = ref(false);
|
const deleteVisible = ref(false);
|
||||||
const deleteTitle = ref('');
|
const deleteTitle = ref('');
|
||||||
|
|
||||||
const enterpriseInfo = store.enterpriseInfo;
|
const enterpriseInfo = computed(() => {
|
||||||
|
return store.enterpriseInfo ?? {};
|
||||||
|
});
|
||||||
|
|
||||||
const okText = computed(() => {
|
const okText = computed(() => {
|
||||||
if (!canAddAccount.value) {
|
if (!canAddAccount.value) {
|
||||||
@ -109,9 +111,9 @@ const okText = computed(() => {
|
|||||||
});
|
});
|
||||||
const customerServiceVisible = ref(false);
|
const customerServiceVisible = ref(false);
|
||||||
const canAddAccount = computed(() => {
|
const canAddAccount = computed(() => {
|
||||||
if (!enterpriseInfo) return false;
|
if (!enterpriseInfo.value) return false;
|
||||||
|
|
||||||
return enterpriseInfo.sub_account_quota > enterpriseInfo.used_sub_account_count;
|
return enterpriseInfo.value.sub_account_quota > enterpriseInfo.value.used_sub_account_count;
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentSelectAccount = ref();
|
const currentSelectAccount = ref();
|
||||||
@ -130,10 +132,10 @@ function handlePageSizeChange(pageSize: number) {
|
|||||||
|
|
||||||
async function getSubAccount() {
|
async function getSubAccount() {
|
||||||
const res = await fetchSubAccountPage(params);
|
const res = await fetchSubAccountPage(params);
|
||||||
const { data, total, code } = res.data;
|
const { data, code } = res;
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
pagination.total = total;
|
pagination.total = data.total;
|
||||||
data.value = data;
|
dataSource.value = data?.data ?? [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function handleAddAccount() {
|
async function handleAddAccount() {
|
||||||
|
|||||||
@ -48,7 +48,9 @@ const form = reactive({
|
|||||||
name: '',
|
name: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const enterpriseInfo = store.enterpriseInfo;
|
const enterpriseInfo = computed(() => {
|
||||||
|
return store.enterpriseInfo ?? {};
|
||||||
|
});
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@ -65,11 +67,13 @@ const infoVisible = ref(false);
|
|||||||
const customerServiceVisible = ref(false);
|
const customerServiceVisible = ref(false);
|
||||||
|
|
||||||
const dataSource = computed(() => {
|
const dataSource = computed(() => {
|
||||||
return enterpriseInfo ? [enterpriseInfo] : [];
|
return enterpriseInfo.value ? [enterpriseInfo.value] : [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log({ dataSource });
|
||||||
const canUpdate = computed(() => {
|
const canUpdate = computed(() => {
|
||||||
if (!enterpriseInfo) return false;
|
if (!enterpriseInfo.value) return false;
|
||||||
return enterpriseInfo.update_name_quota > enterpriseInfo.used_update_name_count;
|
return enterpriseInfo.value.update_name_quota > enterpriseInfo.value.used_update_name_count;
|
||||||
});
|
});
|
||||||
|
|
||||||
const okText = computed(() => {
|
const okText = computed(() => {
|
||||||
@ -81,7 +85,7 @@ const okText = computed(() => {
|
|||||||
|
|
||||||
function handleUpdate() {
|
function handleUpdate() {
|
||||||
if (!canUpdate.value) {
|
if (!canUpdate.value) {
|
||||||
form.name = enterpriseInfo!.name;
|
form.name = enterpriseInfo.value?.name;
|
||||||
}
|
}
|
||||||
infoVisible.value = true;
|
infoVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
:wrapper-col-props="{ span: 21, offset: 0 }"
|
:wrapper-col-props="{ span: 21, offset: 0 }"
|
||||||
>
|
>
|
||||||
<a-form-item field="head_image" label="头像">
|
<a-form-item field="head_image" label="头像">
|
||||||
<div class="flex item-center">
|
<div class="flex items-center">
|
||||||
<a-avatar :image-url="userInfoForm.file_url" :size="48" />
|
<a-avatar :image-url="userInfoForm.file_url" :size="48" />
|
||||||
<span class="upload-button" @click="triggerFileInput">
|
<span class="upload-button" @click="triggerFileInput">
|
||||||
<input
|
<input
|
||||||
@ -91,7 +91,9 @@ import axios from 'axios';
|
|||||||
import { useUserStore } from '@/stores';
|
import { useUserStore } from '@/stores';
|
||||||
|
|
||||||
const store = useUserStore();
|
const store = useUserStore();
|
||||||
const userInfo = store.userInfo;
|
const userInfo = computed(() => {
|
||||||
|
return store.userInfo ?? {};
|
||||||
|
});
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@ -115,7 +117,7 @@ const isSendCaptcha = ref(false);
|
|||||||
const uploadInputRef = ref();
|
const uploadInputRef = ref();
|
||||||
|
|
||||||
const dataSource = computed(() => {
|
const dataSource = computed(() => {
|
||||||
return userInfo ? [userInfo] : [];
|
return userInfo.value ? [userInfo.value] : [];
|
||||||
});
|
});
|
||||||
|
|
||||||
// 表单校验规则
|
// 表单校验规则
|
||||||
|
|||||||
@ -21,13 +21,13 @@
|
|||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-row flex">
|
<div class="filter-row flex">
|
||||||
<a-button class="w-84px search-btn mr-12px" size="medium" @click="handleSearch">
|
<a-button type="outline" class="mr-12px" size="medium" @click="handleSearch">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-search />
|
<icon-search />
|
||||||
</template>
|
</template>
|
||||||
<template #default>搜索</template>
|
<template #default>搜索</template>
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button class="w-84px reset-btn" size="medium" @click="handleReset">
|
<a-button size="medium" @click="handleReset">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-refresh />
|
<icon-refresh />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -3,23 +3,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
:deep(.search-btn) {
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid var(--Brand-Brand-6, #6d4cfe);
|
|
||||||
color: #6d4cfe;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.edit-btn) {
|
:deep(.edit-btn) {
|
||||||
border: 1px solid var(--Brand-Brand-6, #6d4cfe);
|
border: 1px solid var(--Brand-Brand-6, #6d4cfe);
|
||||||
color: #6d4cfe;
|
color: #6d4cfe;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.reset-btn) {
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid var(--BG-500, #b1b2b5);
|
|
||||||
background: var(--BG-white, #fff);
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-wrap {
|
.table-wrap {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
|||||||
@ -1,29 +1,4 @@
|
|||||||
.arco-input-wrapper,
|
|
||||||
.arco-select-view-single,
|
|
||||||
.arco-textarea-wrapper,
|
|
||||||
.arco-picker,
|
|
||||||
.arco-select-view-multiple {
|
|
||||||
border-radius: 4px;
|
|
||||||
border-color: #d7d7d9;
|
|
||||||
background-color: #fff;
|
|
||||||
&:focus-within,
|
|
||||||
&.arco-input-focus,
|
|
||||||
&.arco-textarea-focus {
|
|
||||||
background-color: var(--color-bg-2);
|
|
||||||
border-color: rgb(var(--primary-6));
|
|
||||||
box-shadow: 0 0 0 0 var(--color-primary-light-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.arco-modal {
|
.arco-modal {
|
||||||
.cancel-btn {
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid var(--BG-500, #b1b2b5);
|
|
||||||
background: var(--BG-white, #fff);
|
|
||||||
&:hover {
|
|
||||||
border: 1px solid var(--BG-500, #b1b2b5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.arco-modal-body {
|
.arco-modal-body {
|
||||||
.arco-form-item {
|
.arco-form-item {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
|||||||
@ -25,13 +25,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filter-row flex">
|
<div class="filter-row flex">
|
||||||
<a-button class="w-84px search-btn mr-12px" size="medium">
|
<a-button type="outline" class="mr-12px" size="medium">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-search />
|
<icon-search />
|
||||||
</template>
|
</template>
|
||||||
<template #default>搜索</template>
|
<template #default>搜索</template>
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button class="w-84px reset-btn" size="medium">
|
<a-button size="medium">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-refresh />
|
<icon-refresh />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -25,13 +25,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filter-row flex">
|
<div class="filter-row flex">
|
||||||
<a-button class="w-84px search-btn mr-12px" size="medium">
|
<a-button type="outline" class="mr-12px" size="medium">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-search />
|
<icon-search />
|
||||||
</template>
|
</template>
|
||||||
<template #default>搜索</template>
|
<template #default>搜索</template>
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button class="w-84px reset-btn" size="medium">
|
<a-button size="medium">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-refresh />
|
<icon-refresh />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -15,11 +15,11 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<a-button class="w-110px search-btn mr-12px" size="medium" @click="handleExport">
|
<a-button type="outline" class="w-110px mr-12px" size="medium" @click="handleExport">
|
||||||
<template #icon> <icon-download /> </template>
|
<template #icon> <icon-download /> </template>
|
||||||
<template #default>导出数据</template>
|
<template #default>导出数据</template>
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button class="w-110px search-btn" size="medium" @click="openCustomColumn">
|
<a-button type="outline" class="w-110px" size="medium" @click="openCustomColumn">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<img :src="icon1" width="14" height="14" />
|
<img :src="icon1" width="14" height="14" />
|
||||||
</template>
|
</template>
|
||||||
@ -77,11 +77,7 @@
|
|||||||
{{ record.platform === 0 ? '抖音' : record.platform === 1 ? '小红书' : '-' }}
|
{{ record.platform === 0 ? '抖音' : record.platform === 1 ? '小红书' : '-' }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.dataIndex === 'status'" #cell="{ record }">
|
<template v-else-if="column.dataIndex === 'status'" #cell="{ record }">
|
||||||
<div class="status-tag" :class="`status-tag-${record.status}`">
|
<StatusBox :item="record" class="w-fit h-28px" />
|
||||||
<span class="cts status-tag-text">{{
|
|
||||||
STATUS_LIST.find((item) => item.value === record.status)?.label
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.dataIndex === 'ai_evaluate'" #cell="{ record }">
|
<template v-else-if="column.dataIndex === 'ai_evaluate'" #cell="{ record }">
|
||||||
<div class="ai-evaluation-row flex">
|
<div class="ai-evaluation-row flex">
|
||||||
@ -119,7 +115,7 @@
|
|||||||
}}
|
}}
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.dataIndex === 'operation'" #cell="{ record }">
|
<template v-else-if="column.dataIndex === 'operation'" #cell="{ record }">
|
||||||
<a-button type="outline" size="small" class="search-btn" @click="handleDetail(record)">详情</a-button>
|
<a-button type="outline" size="small" @click="handleDetail(record)">详情</a-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else-if="column.isRateField" #cell="{ record }">
|
<template v-else-if="column.isRateField" #cell="{ record }">
|
||||||
@ -148,7 +144,7 @@
|
|||||||
</a-table-column>
|
</a-table-column>
|
||||||
<a-table-column data-index="operation" fixed="right" width="100" title="操作">
|
<a-table-column data-index="operation" fixed="right" width="100" title="操作">
|
||||||
<template #cell="{ record }">
|
<template #cell="{ record }">
|
||||||
<a-button type="outline" size="small" class="search-btn" @click="handleDetail(record)">详情</a-button>
|
<a-button type="outline" size="small" @click="handleDetail(record)">详情</a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-table-column>
|
</a-table-column>
|
||||||
</template>
|
</template>
|
||||||
@ -167,7 +163,7 @@ import { ref, computed } from 'vue';
|
|||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { getCustomColumns } from '@/api/all/common';
|
import { getCustomColumns } from '@/api/all/common';
|
||||||
import { STATUS_LIST } from '@/views/property-marketing/media-account/components/status-select/constants';
|
import StatusBox from '@/views/property-marketing/media-account/components/status-select/status-box.tsx';
|
||||||
import { formatTableField, formatNumberShow, exactFormatTime } from '@/utils/tools';
|
import { formatTableField, formatNumberShow, exactFormatTime } from '@/utils/tools';
|
||||||
import { getDefaultColumns, getPropPrefix } from '@/views/property-marketing/media-account/account-dashboard/constants';
|
import { getDefaultColumns, getPropPrefix } from '@/views/property-marketing/media-account/account-dashboard/constants';
|
||||||
import CustomTableColumnModal from '../custom-column-modal';
|
import CustomTableColumnModal from '../custom-column-modal';
|
||||||
|
|||||||
@ -15,42 +15,11 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
}
|
}
|
||||||
.status-tag {
|
:deep(.status-box) {
|
||||||
width: fit-content;
|
.label {
|
||||||
display: flex;
|
font-family: $font-family-medium;
|
||||||
height: 28px;
|
font-size: 14px;
|
||||||
padding: 0px 8px;
|
line-height: 22px;
|
||||||
align-items: center;
|
|
||||||
border-radius: 2px;
|
|
||||||
background: #f2f3f5;
|
|
||||||
|
|
||||||
.status-tag-text {
|
|
||||||
color: var(--BG-700, #737478);
|
|
||||||
}
|
|
||||||
&-1 {
|
|
||||||
background: #ebf7f2;
|
|
||||||
.status-tag-text {
|
|
||||||
color: #25c883;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-2,
|
|
||||||
&-4,
|
|
||||||
&-5,
|
|
||||||
&-6,
|
|
||||||
&-7 {
|
|
||||||
background: #ffe7e4;
|
|
||||||
.status-tag-text {
|
|
||||||
color: #f64b31;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-3 {
|
|
||||||
background: #fff7e5;
|
|
||||||
color: #ffae00;
|
|
||||||
.status-tag-text {
|
|
||||||
color: #ffae00;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ai-evaluation-row {
|
.ai-evaluation-row {
|
||||||
|
|||||||
@ -65,7 +65,7 @@
|
|||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div style="text-align: right">
|
<div style="text-align: right">
|
||||||
<a-button class="mr-8px cancel-btn" size="medium" @click="close">取消</a-button>
|
<a-button class="mr-8px" size="medium" @click="close">取消</a-button>
|
||||||
<a-button type="primary" size="medium" @click="onSubmit">确定</a-button>
|
<a-button type="primary" size="medium" @click="onSubmit">确定</a-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -87,13 +87,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.arco-modal-footer {
|
.arco-modal-footer {
|
||||||
.cancel-btn {
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid var(--BG-500, #b1b2b5);
|
|
||||||
background: var(--BG-white, #fff);
|
|
||||||
&:hover {
|
|
||||||
border: 1px solid var(--BG-500, #b1b2b5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
<div class="filter-row-item flex items-center">
|
<div class="filter-row-item flex items-center">
|
||||||
<span class="label">分组</span>
|
<span class="label">分组</span>
|
||||||
<a-space class="w-200px">
|
<a-space class="w-200px">
|
||||||
<GroupSelect v-model="query.group_ids" multiple :options="groups" @change="handleSearch" />
|
<CommonSelect v-model="query.group_ids" :options="groups" @change="handleSearch" />
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-row-item flex items-center">
|
<div class="filter-row-item flex items-center">
|
||||||
@ -31,7 +31,7 @@
|
|||||||
<div class="filter-row-item flex items-center">
|
<div class="filter-row-item flex items-center">
|
||||||
<span class="label">运营人员</span>
|
<span class="label">运营人员</span>
|
||||||
<a-space class="w-160px">
|
<a-space class="w-160px">
|
||||||
<OperatorSelect v-model="query.operator_id" :options="operators" @change="handleSearch" />
|
<CommonSelect v-model="query.operator_id" :multiple="false" :options="operators" @change="handleSearch" />
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -47,13 +47,13 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
<a-button class="w-84px search-btn mr-12px" size="medium" @click="handleSearch">
|
<a-button type="outline" class="w-84px mr-12px" size="medium" @click="handleSearch">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-search />
|
<icon-search />
|
||||||
</template>
|
</template>
|
||||||
<template #default>搜索</template>
|
<template #default>搜索</template>
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button class="w-84px reset-btn" size="medium" @click="handleReset">
|
<a-button class="w-84px" size="medium" @click="handleReset">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-refresh />
|
<icon-refresh />
|
||||||
</template>
|
</template>
|
||||||
@ -66,9 +66,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, defineEmits, defineProps } from 'vue';
|
import { reactive, defineEmits, defineProps } from 'vue';
|
||||||
import { fetchAccountGroups, fetchAccountOperators } from '@/api/all/propertyMarketing';
|
import { fetchAccountGroups, fetchAccountOperators } from '@/api/all/propertyMarketing';
|
||||||
import GroupSelect from '@/views/property-marketing/media-account/components/group-select';
|
|
||||||
import OperatorSelect from '@/views/property-marketing/media-account/components/operator-select';
|
|
||||||
import StatusSelect from '@/views/property-marketing/media-account/components/status-select';
|
import StatusSelect from '@/views/property-marketing/media-account/components/status-select';
|
||||||
|
import CommonSelect from '@/components/common-select';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
query: {
|
query: {
|
||||||
|
|||||||
@ -1,17 +1,4 @@
|
|||||||
.container {
|
.container {
|
||||||
:deep(.arco-input-wrapper),
|
|
||||||
:deep(.arco-select-view-single),
|
|
||||||
:deep(.arco-select-view-multiple) {
|
|
||||||
border-radius: 4px;
|
|
||||||
border-color: #d7d7d9;
|
|
||||||
background-color: #fff;
|
|
||||||
&:focus-within,
|
|
||||||
&.arco-input-focus {
|
|
||||||
background-color: var(--color-bg-2);
|
|
||||||
border-color: rgb(var(--primary-6));
|
|
||||||
box-shadow: 0 0 0 0 var(--color-primary-light-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.filter-row {
|
.filter-row {
|
||||||
.filter-row-item {
|
.filter-row-item {
|
||||||
&:not(:last-child) {
|
&:not(:last-child) {
|
||||||
|
|||||||
@ -65,7 +65,8 @@ import AccountTable from './components/account-table';
|
|||||||
import { getAccountBoardOverview, getAccountBoardList, postAccountBoardExport } from '@/api/all/propertyMarketing';
|
import { getAccountBoardOverview, getAccountBoardList, postAccountBoardExport } from '@/api/all/propertyMarketing';
|
||||||
import { formatNumberShow } from '@/utils/tools';
|
import { formatNumberShow } from '@/utils/tools';
|
||||||
import { INITIAL_QUERY, CARD_FIELDS } from './constants';
|
import { INITIAL_QUERY, CARD_FIELDS } from './constants';
|
||||||
import { downloadByUrl } from '@/utils/tools';
|
// import { downloadByUrl } from '@/utils/tools';
|
||||||
|
import { showExportNotification } from '@/utils/arcoD';
|
||||||
|
|
||||||
import icon1 from '@/assets/img/icon-question.png';
|
import icon1 from '@/assets/img/icon-question.png';
|
||||||
|
|
||||||
@ -139,7 +140,7 @@ const handleExport = () => {
|
|||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
const { code, data } = res;
|
const { code, data } = res;
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
downloadByUrl(data.download_url);
|
showExportNotification(`正在下载“${data.name}”,请稍后...`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,16 +2,6 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
:deep(.search-btn) {
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid var(--Brand-Brand-6, #6d4cfe);
|
|
||||||
color: #6d4cfe;
|
|
||||||
}
|
|
||||||
:deep(.reset-btn) {
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid var(--BG-500, #b1b2b5);
|
|
||||||
background: var(--BG-white, #fff);
|
|
||||||
}
|
|
||||||
.filter-wrap {
|
.filter-wrap {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid #e6e6e8;
|
border: 1px solid #e6e6e8;
|
||||||
|
|||||||
@ -8,14 +8,11 @@
|
|||||||
<span class="cts !text-18px !lh-26px title">账号信息</span>
|
<span class="cts !text-18px !lh-26px title">账号信息</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="account-info-box">
|
<div class="account-info-box">
|
||||||
|
<div class="grid grid-cols-4">
|
||||||
<div
|
<div
|
||||||
v-for="(row, rowIdx) in getAccountInfoFields(dateType, showMore)"
|
v-for="(field, colIdx) in getAccountInfoFields(dateType, showMore)"
|
||||||
:key="rowIdx"
|
|
||||||
class="grid grid-cols-4 mb-24px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="(field, colIdx) in row"
|
|
||||||
:key="colIdx"
|
:key="colIdx"
|
||||||
|
class="mb-24px"
|
||||||
:class="field.dataIndex === 'ai_evaluation' ? 'col-span-2' : ''"
|
:class="field.dataIndex === 'ai_evaluation' ? 'col-span-2' : ''"
|
||||||
>
|
>
|
||||||
<template v-if="field.dataIndex === 'ai_evaluation'">
|
<template v-if="field.dataIndex === 'ai_evaluation'">
|
||||||
@ -56,11 +53,29 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="cts">
|
<p class="cts">
|
||||||
<template v-if="field.type === 'status'">
|
<template v-if="field.type === 'status'">
|
||||||
<div class="status-tag" :class="`status-tag-${detailData.status}`">
|
<StatusBox :item="detailData" class="w-fit" />
|
||||||
<span class="cts status-tag-text">{{
|
</template>
|
||||||
STATUS_LIST.find((item) => item.value === detailData.status)?.label
|
<template v-else-if="field.dataIndex === 'tags'">
|
||||||
}}</span>
|
<div v-if="detailData.tags?.length" class="flex items-center">
|
||||||
|
<div v-for="(tag, index) in detailData.tags.slice(0, 2)" :key="index" class="tag-box">
|
||||||
|
<span class="text">{{ tag.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<a-tooltip
|
||||||
|
v-if="detailData.tags.length > 2"
|
||||||
|
position="top"
|
||||||
|
:content="
|
||||||
|
detailData.tags
|
||||||
|
.slice(2)
|
||||||
|
.map((v) => v.name)
|
||||||
|
.join(',')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="tag-box">
|
||||||
|
<span class="text">{{ `+${detailData.tags.length - 2}` }}</span>
|
||||||
|
</div>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
<span class="cts" v-else>-</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="field.dataIndex === 'like_collect_number'">
|
<template v-else-if="field.dataIndex === 'like_collect_number'">
|
||||||
{{
|
{{
|
||||||
@ -72,6 +87,12 @@
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="field.dataIndex === 'platform'">
|
||||||
|
<img :src="detailData.platform === 0 ? icon5 : icon6" width="16" height="16" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="field.dataIndex === 'last_synced_at'">
|
||||||
|
{{ exactFormatTime(detailData.last_synced_at, 'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss') }}
|
||||||
|
</template>
|
||||||
<!-- 环比字段特殊渲染 -->
|
<!-- 环比字段特殊渲染 -->
|
||||||
<template v-else-if="field.isRateField">
|
<template v-else-if="field.isRateField">
|
||||||
<div
|
<div
|
||||||
@ -83,6 +104,9 @@
|
|||||||
{{ `${detailData[field.dataIndex]}%` }}
|
{{ `${detailData[field.dataIndex]}%` }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="field.dataIndex === 'id'">
|
||||||
|
{{ detailData.id }}
|
||||||
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ formatTableField(field, detailData, true) }}
|
{{ formatTableField(field, detailData, true) }}
|
||||||
</template>
|
</template>
|
||||||
@ -108,10 +132,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
import { formatTableField, formatNumberShow } from '@/utils/tools';
|
import { formatTableField, formatNumberShow, exactFormatTime } from '@/utils/tools';
|
||||||
import { getAccountInfoFields } from '../../constants';
|
import { getAccountInfoFields } from '../../constants';
|
||||||
import { STATUS_LIST } from '@/views/property-marketing/media-account/components/status-select/constants';
|
|
||||||
import { getPropPrefix } from '@/views/property-marketing/media-account/account-dashboard/constants';
|
import { getPropPrefix } from '@/views/property-marketing/media-account/account-dashboard/constants';
|
||||||
|
import StatusBox from '@/views/property-marketing/media-account/components/status-select/status-box.tsx';
|
||||||
|
|
||||||
import { getAccountBoardDetail } from '@/api/all/propertyMarketing';
|
import { getAccountBoardDetail } from '@/api/all/propertyMarketing';
|
||||||
|
|
||||||
@ -119,10 +143,13 @@ import icon1 from '@/assets/img/media-account/icon5.png';
|
|||||||
import icon2 from '@/assets/img/media-account/icon-warn.png';
|
import icon2 from '@/assets/img/media-account/icon-warn.png';
|
||||||
import icon3 from '@/assets/img/media-account/icon-warn-1.png';
|
import icon3 from '@/assets/img/media-account/icon-warn-1.png';
|
||||||
import icon4 from '@/assets/img/media-account/icon-success.png';
|
import icon4 from '@/assets/img/media-account/icon-success.png';
|
||||||
|
import icon5 from '@/assets/img/media-account/icon-dy.png';
|
||||||
|
import icon6 from '@/assets/img/media-account/icon-xhs.png';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const id = route.params.id;
|
const id = route.params.id;
|
||||||
const dateType = route.query.type;
|
const dateType = route.query.type ?? 'week';
|
||||||
|
|
||||||
const detailData = ref({});
|
const detailData = ref({});
|
||||||
const showMore = ref(false);
|
const showMore = ref(false);
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
.account-info-wrap {
|
.account-info-wrap {
|
||||||
|
@mixin ellipsis {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
position: relative;
|
position: relative;
|
||||||
.status-tag {
|
.status-tag {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
@ -38,4 +43,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.tag-box {
|
||||||
|
display: flex;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0px 4px;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: var(--BG-200, #f2f3f5);
|
||||||
|
max-width: 100px;
|
||||||
|
.text {
|
||||||
|
@include ellipsis();
|
||||||
|
color: var(--Text-2, #3c4043);
|
||||||
|
font-family: $font-family-medium;
|
||||||
|
font-size: 10px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -70,12 +70,12 @@ export const TABLE_COLUMNS = [
|
|||||||
align: 'right',
|
align: 'right',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
// {
|
||||||
title: '封面点击率',
|
// title: '封面点击率',
|
||||||
dataIndex: 'cover_click_rate',
|
// dataIndex: 'cover_click_rate',
|
||||||
width: 180,
|
// width: 180,
|
||||||
tooltip: '内容在被曝光后,用户点击进入的比例,反映封面与标题吸引力。',
|
// tooltip: '内容在被曝光后,用户点击进入的比例,反映封面与标题吸引力。',
|
||||||
align: 'right',
|
// align: 'right',
|
||||||
suffix: '%',
|
// suffix: '%',
|
||||||
},
|
// },
|
||||||
];
|
];
|
||||||
|
|||||||
@ -36,13 +36,13 @@
|
|||||||
/>
|
/>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
<a-button class="w-84px search-btn mr-12px" size="medium" @click="handleSearch">
|
<a-button type="outline" class="w-84px mr-12px" size="medium" @click="handleSearch">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-search />
|
<icon-search />
|
||||||
</template>
|
</template>
|
||||||
<template #default>搜索</template>
|
<template #default>搜索</template>
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button class="w-84px reset-btn" size="medium" @click="handleReset">
|
<a-button class="w-84px" size="medium" @click="handleReset">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-refresh />
|
<icon-refresh />
|
||||||
</template>
|
</template>
|
||||||
@ -87,6 +87,9 @@
|
|||||||
<template v-if="column.dataIndex === 'published_at'">
|
<template v-if="column.dataIndex === 'published_at'">
|
||||||
{{ exactFormatTime(record.published_at) }}
|
{{ exactFormatTime(record.published_at) }}
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="column.dataIndex === 'exposure_number'">
|
||||||
|
{{ formatNumberShow({ value: record.view_number * 10, showExactValue: true }) }}
|
||||||
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ formatTableField(column, record, true) }}
|
{{ formatTableField(column, record, true) }}
|
||||||
</template>
|
</template>
|
||||||
@ -113,7 +116,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { TABLE_COLUMNS, INITIAL_QUERY, INITIAL_PAGE_INFO } from './constants';
|
import { TABLE_COLUMNS, INITIAL_QUERY, INITIAL_PAGE_INFO } from './constants';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { formatTableField, exactFormatTime } from '@/utils/tools';
|
import { formatTableField, exactFormatTime, formatNumberShow } from '@/utils/tools';
|
||||||
import { getMediaAccountBoardWorks } from '@/api/all/propertyMarketing';
|
import { getMediaAccountBoardWorks } from '@/api/all/propertyMarketing';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|||||||
@ -1,18 +1,4 @@
|
|||||||
.note-table-wrap {
|
.note-table-wrap {
|
||||||
:deep(.arco-input-wrapper),
|
|
||||||
:deep(.arco-select-view-single),
|
|
||||||
:deep(.arco-select-view-multiple),
|
|
||||||
:deep(.arco-picker) {
|
|
||||||
border-radius: 4px;
|
|
||||||
border-color: #d7d7d9;
|
|
||||||
background-color: #fff;
|
|
||||||
&:focus-within,
|
|
||||||
&.arco-input-focus {
|
|
||||||
background-color: var(--color-bg-2);
|
|
||||||
border-color: rgb(var(--primary-6));
|
|
||||||
box-shadow: 0 0 0 0 var(--color-primary-light-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.filter-row {
|
.filter-row {
|
||||||
.filter-row-item {
|
.filter-row-item {
|
||||||
&:not(:last-child) {
|
&:not(:last-child) {
|
||||||
|
|||||||
@ -4,50 +4,47 @@
|
|||||||
*/
|
*/
|
||||||
import { CUSTOM_FIELDS, getPropPrefix } from '@/views/property-marketing/media-account/common_constants';
|
import { CUSTOM_FIELDS, getPropPrefix } from '@/views/property-marketing/media-account/common_constants';
|
||||||
|
|
||||||
// 不足4个。就补两个null进去
|
export function groupFieldsWithColSpan<
|
||||||
export function groupArrayBySize<T extends { dataIndex: string; prop: string; title: string; tooltip: string }>(
|
T extends { dataIndex: string; prop?: string; title: string; tooltip?: string; notDifferentiateDateType?: boolean },
|
||||||
fields: T[],
|
>(fields: T[], dateType: string): (T & { colSpan: number })[] {
|
||||||
groupSize = 4,
|
|
||||||
dateType: string,
|
|
||||||
): T[][] {
|
|
||||||
const result: T[][] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < fields.length; i += groupSize) {
|
|
||||||
result.push(fields.slice(i, i + groupSize));
|
|
||||||
}
|
|
||||||
const labelPrefix = dateType === 'week' ? '近7天' : '近30天';
|
const labelPrefix = dateType === 'week' ? '近7天' : '近30天';
|
||||||
|
return fields.map((item) => {
|
||||||
return result.map((item) => {
|
const newItem = { ...item, colSpan: item.dataIndex === 'ai_evaluation' ? 2 : 1 };
|
||||||
return item.map((item) => {
|
if (item.notDifferentiateDateType) {
|
||||||
|
return newItem;
|
||||||
|
} else {
|
||||||
return {
|
return {
|
||||||
...item,
|
...newItem,
|
||||||
dataIndex: `${getPropPrefix(dateType)}${item.dataIndex}`,
|
dataIndex: `${getPropPrefix(dateType)}${item.dataIndex}`,
|
||||||
prop: `${getPropPrefix(dateType)}${item.prop}`,
|
prop: `${getPropPrefix(dateType)}${item.prop}`,
|
||||||
title: `${labelPrefix}${item.title}`,
|
title: `${labelPrefix}${item.title}`,
|
||||||
tooltip: `${labelPrefix}${item.tooltip}`,
|
tooltip: `${labelPrefix}${item.tooltip}`
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAccountInfoFields = (dateType: string, showMore: boolean) => {
|
export const getAccountInfoFields = (dateType: string, showMore: boolean) => {
|
||||||
const baseFields = [
|
const baseFields = [
|
||||||
[
|
{ title: '账号名称', dataIndex: 'name', notDifferentiateDateType: true },
|
||||||
{ title: '账号名称', dataIndex: 'name' },
|
{ title: '数据更新时间', dataIndex: 'last_synced_at', notDifferentiateDateType: true },
|
||||||
{ title: '项目分组', dataIndex: 'group.name' },
|
{ title: '平台', dataIndex: 'platform', notDifferentiateDateType: true },
|
||||||
{ title: '状态', dataIndex: 'status', type: 'status' },
|
{ title: '状态', dataIndex: 'status', type: 'status', notDifferentiateDateType: true },
|
||||||
{ title: '运营人员', dataIndex: 'operator.name' },
|
{ title: '账号ID', dataIndex: 'account_id', notDifferentiateDateType: true },
|
||||||
],
|
{ title: '手机号码', dataIndex: 'mobile', notDifferentiateDateType: true },
|
||||||
[
|
{ title: '运营人员', dataIndex: 'operator.name', notDifferentiateDateType: true },
|
||||||
{ title: 'AI评价', dataIndex: 'ai_evaluation' },
|
{ title: '所属项目', dataIndex: 'group.name', notDifferentiateDateType: true },
|
||||||
{ title: '粉丝量', dataIndex: 'fans_number', tooltip: '账号的当前粉丝总数。' },
|
{ title: '分组', dataIndex: 'group.name', notDifferentiateDateType: true },
|
||||||
|
{ title: '标签', dataIndex: 'tags', notDifferentiateDateType: true },
|
||||||
|
{ title: 'AI评价', dataIndex: 'ai_evaluation', notDifferentiateDateType: true },
|
||||||
|
{ title: '粉丝量', dataIndex: 'fans_number', tooltip: '账号的当前粉丝总数。', notDifferentiateDateType: true },
|
||||||
{
|
{
|
||||||
title: '总赞藏数',
|
title: '总赞藏数',
|
||||||
|
notDifferentiateDateType: true,
|
||||||
dataIndex: 'like_collect_number',
|
dataIndex: 'like_collect_number',
|
||||||
tooltip: '账号所有内容获得的点赞数与收藏数总和,用于衡量历史内容的整体吸引力与认可度。',
|
tooltip: '账号所有内容获得的点赞数与收藏数总和,用于衡量历史内容的整体吸引力与认可度。',
|
||||||
},
|
},
|
||||||
],
|
|
||||||
];
|
];
|
||||||
const customFields = groupArrayBySize(CUSTOM_FIELDS, 4, dateType);
|
const allFields = showMore ? [...baseFields, ...CUSTOM_FIELDS] : baseFields.slice(0,8);
|
||||||
return showMore ? [...baseFields, ...customFields] : [...baseFields];
|
return groupFieldsWithColSpan(allFields, dateType);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import { useRouter } from 'vue-router';
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
router.push('/media-account/dashboard');
|
router.go(-1);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -19,16 +19,6 @@
|
|||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
:deep(.search-btn) {
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid var(--Brand-Brand-6, #6d4cfe);
|
|
||||||
color: #6d4cfe;
|
|
||||||
}
|
|
||||||
:deep(.reset-btn) {
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid var(--BG-500, #b1b2b5);
|
|
||||||
background: var(--BG-white, #fff);
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-wrap {
|
.table-wrap {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
<span>确认删除 {{ accountName }} 这个账号吗?</span>
|
<span>确认删除 {{ accountName }} 这个账号吗?</span>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<a-button class="cancel-btn" size="large" @click="onClose">取消</a-button>
|
<a-button size="large" @click="onClose">取消</a-button>
|
||||||
<a-button type="primary" class="ml-16px danger-btn" status="danger" size="large" @click="onDelete"
|
<a-button type="primary" class="ml-16px danger-btn" status="danger" size="large" @click="onDelete"
|
||||||
>确认删除</a-button
|
>确认删除</a-button
|
||||||
>
|
>
|
||||||
|
|||||||
@ -0,0 +1,168 @@
|
|||||||
|
import {
|
||||||
|
EnumStatus,
|
||||||
|
EnumErrorStatus,
|
||||||
|
getStatusInfo,
|
||||||
|
} from '@/views/property-marketing/media-account/components/status-select/status-box';
|
||||||
|
import { Dropdown, Doption, Button, Tooltip } from '@arco-design/web-vue';
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FooterBtn',
|
||||||
|
props: {
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['syncData', 'handleReauthorize', 'handlePause', 'openDelete', 'openEdit'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const statusInfo = computed(() => {
|
||||||
|
const { status, error_status, to_be_expire_for_cookie } = props.item;
|
||||||
|
return getStatusInfo(status, error_status, to_be_expire_for_cookie) ?? {};
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderEditDoption = () => {
|
||||||
|
return (
|
||||||
|
<Doption class="color-#211F24" onClick={() => emit('openEdit', props.item)}>
|
||||||
|
编辑
|
||||||
|
</Doption>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const renderReauthorizeDoption = (text = '重新授权') => {
|
||||||
|
return (
|
||||||
|
<Doption class="color-#211F24" onClick={() => emit('handleReauthorize', props.item)}>
|
||||||
|
{text}
|
||||||
|
</Doption>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const renderPauseDoption = () => {
|
||||||
|
return (
|
||||||
|
<Doption class="color-#211F24" onClick={() => emit('handlePause', props.item)}>
|
||||||
|
暂停同步
|
||||||
|
</Doption>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const renderUpdateBtn = () => {
|
||||||
|
return (
|
||||||
|
<Button type="outline" size="mini" onClick={() => emit('syncData', props.item)}>
|
||||||
|
更新数据
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const renderDeleteDoption = () => {
|
||||||
|
return (
|
||||||
|
<Doption class="color-#F64B31" onClick={() => emit('openDelete', props.item)}>
|
||||||
|
删除
|
||||||
|
</Doption>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const renderNormal = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dropdown
|
||||||
|
trigger="hover"
|
||||||
|
v-slots={{
|
||||||
|
default: () => (
|
||||||
|
<Button type="outline" class="mr-8px" size="mini">
|
||||||
|
更多
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
content: () => (
|
||||||
|
<>
|
||||||
|
{renderEditDoption()}
|
||||||
|
{renderReauthorizeDoption()}
|
||||||
|
{renderPauseDoption()}
|
||||||
|
{renderDeleteDoption()}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
></Dropdown>
|
||||||
|
{renderUpdateBtn()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const renderPause = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dropdown
|
||||||
|
trigger="hover"
|
||||||
|
v-slots={{
|
||||||
|
default: () => (
|
||||||
|
<Button type="outline" class="mr-8px" size="mini">
|
||||||
|
更多
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
content: () => (
|
||||||
|
<>
|
||||||
|
{renderEditDoption()}
|
||||||
|
{renderDeleteDoption()}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
></Dropdown>
|
||||||
|
<Button type="outline" size="mini" onClick={() => emit('handleReauthorize', props.item)}>
|
||||||
|
开启同步
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
// 异常情况
|
||||||
|
const renderAbnormal = () => {
|
||||||
|
const { error_status } = props.item;
|
||||||
|
const isMissing = error_status === EnumErrorStatus.MISSING;
|
||||||
|
const isUnauthorized = error_status === EnumErrorStatus.UNAUTHORIZED;
|
||||||
|
|
||||||
|
const renderSyncBtn = () => {
|
||||||
|
if (isMissing) {
|
||||||
|
return renderUpdateBtn();
|
||||||
|
} else if ([EnumErrorStatus.REQUEST, EnumErrorStatus.FREEZE].includes(error_status)) {
|
||||||
|
return (
|
||||||
|
<Tooltip content={statusInfo.value.disabledBtnTooltip}>
|
||||||
|
<Button type="outline" size="mini" disabled>
|
||||||
|
重新授权
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Button type="outline" size="mini" onClick={() => emit('handleReauthorize', props.item)}>
|
||||||
|
{isUnauthorized ? '去授权' : '重新授权'}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dropdown
|
||||||
|
trigger="hover"
|
||||||
|
v-slots={{
|
||||||
|
default: () => (
|
||||||
|
<Button type="outline" class="mr-8px" size="mini">
|
||||||
|
更多
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
content: () => (
|
||||||
|
<>
|
||||||
|
{renderEditDoption()}
|
||||||
|
{isMissing && renderReauthorizeDoption()}
|
||||||
|
{renderPauseDoption()}
|
||||||
|
{renderDeleteDoption()}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
></Dropdown>
|
||||||
|
{renderSyncBtn()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const renderContent = () => {
|
||||||
|
const { status } = props.item;
|
||||||
|
if (status === EnumStatus.NORMAL) {
|
||||||
|
return renderNormal();
|
||||||
|
} else if (status === EnumStatus.PAUSE) {
|
||||||
|
return renderPause();
|
||||||
|
} else {
|
||||||
|
return renderAbnormal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return () => renderContent();
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -4,20 +4,31 @@
|
|||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<div class="card-container">
|
<div class="card-container">
|
||||||
<div
|
<a-spin
|
||||||
v-for="(item, index) in dataSource"
|
v-for="(item, index) in dataSource"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
:loading="isSyncing(item)"
|
||||||
|
tip="更新数据中..."
|
||||||
class="card-item"
|
class="card-item"
|
||||||
:class="{
|
:class="{
|
||||||
checked: isSelected(item),
|
checked: isSelected(item),
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
<template #icon>
|
||||||
|
<icon-sync size="24" />
|
||||||
|
</template>
|
||||||
<a-checkbox :model-value="isSelected(item)" :value="item.id" @change="toggleSelect(item)"></a-checkbox>
|
<a-checkbox :model-value="isSelected(item)" :value="item.id" @change="toggleSelect(item)"></a-checkbox>
|
||||||
<div class="ml-8px flex-1">
|
<div class="ml-8px flex-1">
|
||||||
<p class="name">{{ item.name || '-' }}</p>
|
<a-tooltip content="点击查看账号详情">
|
||||||
|
<p class="name cursor-pointer hover:!color-#6d4cfe" @click="goDetail(item)">{{ item.name || '-' }}</p>
|
||||||
|
</a-tooltip>
|
||||||
<div class="field-row">
|
<div class="field-row">
|
||||||
<span class="label">状态</span>
|
<span class="label">状态</span>
|
||||||
<StatusBox :status="item.status" />
|
<StatusBox :item="item" />
|
||||||
|
</div>
|
||||||
|
<div class="field-row">
|
||||||
|
<span class="label">数据更新时间</span>
|
||||||
|
<span class="cts num">{{ getLastSyncedAt(item) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-row">
|
<div class="field-row">
|
||||||
<span class="label">平台</span>
|
<span class="label">平台</span>
|
||||||
@ -35,6 +46,30 @@
|
|||||||
<span class="label">运营人员</span>
|
<span class="label">运营人员</span>
|
||||||
<span class="cts">{{ item.operator?.name || '-' }}</span>
|
<span class="cts">{{ item.operator?.name || '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field-row">
|
||||||
|
<span class="label">所属项目</span>
|
||||||
|
<span v-if="!item.projects.length" class="cts">-</span>
|
||||||
|
<div v-else class="flex items-center">
|
||||||
|
<a-tooltip
|
||||||
|
v-if="item.projects.length > 2"
|
||||||
|
position="bottom"
|
||||||
|
:content="
|
||||||
|
item.projects
|
||||||
|
.slice(2)
|
||||||
|
.map((v) => v.name)
|
||||||
|
.join(',')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="tag-box">
|
||||||
|
<span class="text">{{ `+${item.projects.length - 2}` }}</span>
|
||||||
|
</div>
|
||||||
|
</a-tooltip>
|
||||||
|
|
||||||
|
<div v-for="(project, index) in item.projects.slice(0, 2)" :key="index" class="tag-box">
|
||||||
|
<span class="text">{{ project.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="field-row">
|
<div class="field-row">
|
||||||
<span class="label">分组</span>
|
<span class="label">分组</span>
|
||||||
<span class="cts">{{ item.group?.name || '-' }}</span>
|
<span class="cts">{{ item.group?.name || '-' }}</span>
|
||||||
@ -64,48 +99,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="operate-row">
|
<div class="operate-row">
|
||||||
<a-dropdown trigger="hover">
|
<FooterBtn
|
||||||
<a-button class="w-52px search-btn mr-8px" size="mini">
|
:item="item"
|
||||||
<template #default>更多</template>
|
@handleReauthorize="handleReauthorize"
|
||||||
</a-button>
|
@syncData="syncData"
|
||||||
<template #content>
|
@openEdit="openEdit"
|
||||||
<a-doption class="color-#211F24" @click="openEdit(item)">编辑</a-doption>
|
@openDelete="openDelete"
|
||||||
<a-doption v-if="showPauseButton(item.status)" class="color-#211F24" @click="handlePause(item)"
|
@handlePause="handlePause"
|
||||||
>暂停同步</a-doption
|
/>
|
||||||
>
|
|
||||||
<a-doption class="color-#F64B31" @click="openDelete(item)">删除</a-doption>
|
|
||||||
</template>
|
|
||||||
<a-button class="search-btn" size="mini" @click="onBtnClick(item)">
|
|
||||||
<template #default>{{ getBtnText(item) }}</template>
|
|
||||||
</a-button>
|
|
||||||
</a-dropdown>
|
|
||||||
|
|
||||||
<!-- <img :src="icon3" width="16" height="16" class="mr-8px cursor-pointer" @click="openDelete(item)" />
|
|
||||||
<a-button
|
|
||||||
v-if="showPauseButton(item.status)"
|
|
||||||
class="w-64px search-btn mr-8px"
|
|
||||||
size="mini"
|
|
||||||
@click="handlePause(item)"
|
|
||||||
>
|
|
||||||
<template #default>暂停同步</template>
|
|
||||||
</a-button>
|
|
||||||
<a-tooltip v-if="isAbnormalStatus(item.status)" :content="getTooltipText(item.status)">
|
|
||||||
<a-button class="w-64px search-btn mr-8px" size="mini" @click="handleReauthorize(item)">
|
|
||||||
<template #default>重新授权</template>
|
|
||||||
</a-button>
|
|
||||||
</a-tooltip>
|
|
||||||
<template v-else>
|
|
||||||
<a-button class="w-64px search-btn mr-8px" size="mini" @click="handleReauthorize(item)">
|
|
||||||
<template #default>{{ isUnauthorizedStatus(item.status) ? '去授权' : '重新授权' }}</template>
|
|
||||||
</a-button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<a-button class="w-40px search-btn" size="mini" @click="openEdit(item)">
|
|
||||||
<template #default>编辑</template>
|
|
||||||
</a-button> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="isSyncFailed(item)" class="mask">
|
||||||
|
<div class="flex items-center mb-16px box">
|
||||||
|
<img :src="icon3" width="16" height="16" class="mr-8px" />
|
||||||
|
<span class="name !mb-0">{{ getErrorStatusText(item) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<a-button type="outline" class="mr-8px" size="mini" @click="handleCancel(item)">取消</a-button>
|
||||||
|
<a-button type="outline" size="mini" @click="handleConfirm(item)" v-if="showConfirmBtn(item)">{{
|
||||||
|
getConfirmBtnText(item)
|
||||||
|
}}</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
<PauseAccountPatchModal ref="pauseAccountPatchModalRef" @success="emits('update')" />
|
<PauseAccountPatchModal ref="pauseAccountPatchModalRef" @success="emits('update')" />
|
||||||
<ReauthorizeAccountModal ref="reauthorizeAccountModalRef" @update="emits('update')" />
|
<ReauthorizeAccountModal ref="reauthorizeAccountModalRef" @update="emits('update')" />
|
||||||
<AuthorizedAccountModal ref="authorizedAccountModalRef" @update="emits('update')" />
|
<AuthorizedAccountModal ref="authorizedAccountModalRef" @update="emits('update')" />
|
||||||
@ -113,30 +129,49 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, ref, computed } from 'vue';
|
import { defineProps, ref, computed, inject } from 'vue';
|
||||||
import { STATUS_LIST, EnumStatus } from '@/views/property-marketing/media-account/components/status-select/constants';
|
import { useRouter } from 'vue-router';
|
||||||
|
import { deleteSyncStatus } from '@/api/all/propertyMarketing';
|
||||||
|
import { exactFormatTime } from '@/utils/tools';
|
||||||
|
import {
|
||||||
|
EnumErrorStatus,
|
||||||
|
errorStatusMap,
|
||||||
|
EnumStatus,
|
||||||
|
} from '@/views/property-marketing/media-account/components/status-select/status-box';
|
||||||
|
|
||||||
import PauseAccountPatchModal from './pause-account-patch';
|
import PauseAccountPatchModal from './pause-account-patch';
|
||||||
import StatusBox from '../status-box';
|
import StatusBox from '@/views/property-marketing/media-account/components/status-select/status-box.tsx';
|
||||||
import ReauthorizeAccountModal from '../reauthorize-account-modal';
|
import ReauthorizeAccountModal from '../reauthorize-account-modal';
|
||||||
import AuthorizedAccountModal from '../authorized-account-modal';
|
import AuthorizedAccountModal from '../authorized-account-modal';
|
||||||
|
import FooterBtn from './footer-btn';
|
||||||
|
|
||||||
import icon1 from '@/assets/img/media-account/icon-dy.png';
|
import icon1 from '@/assets/img/media-account/icon-dy.png';
|
||||||
import icon2 from '@/assets/img/media-account/icon-xhs.png';
|
import icon2 from '@/assets/img/media-account/icon-xhs.png';
|
||||||
// import icon3 from '@/assets/img/media-account/icon-delete.png';
|
import icon3 from '@/assets/img/media-account/icon-warn.png';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
dataSource: {
|
dataSource: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
syncMediaAccounts: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
selectedItems: {
|
selectedItems: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
isLoadingTaskStatus: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emits = defineEmits(['openEdit', 'update', 'selectionChange', 'delete']);
|
const emits = defineEmits(['openEdit', 'update', 'selectionChange', 'delete', 'updateSyncStatus']);
|
||||||
|
const syncData = inject('handleSyncData');
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const pauseAccountPatchModalRef = ref(null);
|
const pauseAccountPatchModalRef = ref(null);
|
||||||
const reauthorizeAccountModalRef = ref(null);
|
const reauthorizeAccountModalRef = ref(null);
|
||||||
@ -166,9 +201,8 @@ const openDelete = (item) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleReauthorize = (item) => {
|
const handleReauthorize = (item) => {
|
||||||
console.log({ item });
|
const { id, platform, error_status } = item;
|
||||||
const { id, platform, status } = item;
|
const isUnauthorized = isUnauthorizedStatus(error_status);
|
||||||
const isUnauthorized = isUnauthorizedStatus(status);
|
|
||||||
if (isUnauthorized) {
|
if (isUnauthorized) {
|
||||||
authorizedAccountModalRef.value?.open(id, platform);
|
authorizedAccountModalRef.value?.open(id, platform);
|
||||||
} else {
|
} else {
|
||||||
@ -180,51 +214,70 @@ const handlePause = (item) => {
|
|||||||
pauseAccountPatchModalRef.value?.open(item);
|
pauseAccountPatchModalRef.value?.open(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
const showPauseButton = (status) => {
|
const isUnauthorizedStatus = (error_status) => {
|
||||||
return ![EnumStatus.PAUSE, EnumStatus.UNAUTHORIZED].includes(status);
|
return [EnumErrorStatus.UNAUTHORIZED].includes(error_status);
|
||||||
};
|
|
||||||
const isUnauthorizedStatus = (status) => {
|
|
||||||
return [EnumStatus.UNAUTHORIZED].includes(status);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 三种异常情况
|
const goDetail = (item) => {
|
||||||
const isAbnormalStatus = (status) => {
|
router.push(`/media-account/detail/${item.id}`);
|
||||||
return [
|
};
|
||||||
EnumStatus.ABNORMAL,
|
const formatTime = (time) => {
|
||||||
EnumStatus.ABNORMAL_LOGIN,
|
return exactFormatTime(time, 'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss');
|
||||||
EnumStatus.ABNORMAL_REQUEST,
|
|
||||||
EnumStatus.ABNORMAL_FREEZE,
|
|
||||||
].includes(status);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTooltipText = (status) => {
|
const getSyncMediaAccount = (item) => {
|
||||||
return STATUS_LIST.find((v) => v.value === status)?.tooltip ?? '-';
|
return props.syncMediaAccounts.find((v) => v.id === item.id);
|
||||||
};
|
};
|
||||||
|
const isSyncing = (item) => {
|
||||||
|
if (!props.syncMediaAccounts.length) return false;
|
||||||
|
return getSyncMediaAccount(item)?.status === 0;
|
||||||
|
};
|
||||||
|
const getLastSyncedAt = (item) => {
|
||||||
|
const target = getSyncMediaAccount(item);
|
||||||
|
if (props.isLoadingTaskStatus && target) {
|
||||||
|
if (target?.status !== 0) {
|
||||||
|
return formatTime(target.last_synced_at);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return formatTime(item.last_synced_at);
|
||||||
|
};
|
||||||
|
const isSyncFailed = (item) => {
|
||||||
|
return getSyncMediaAccount(item)?.status === 2;
|
||||||
|
};
|
||||||
|
const getErrorStatusText = (item) => {
|
||||||
|
const error_status = getSyncMediaAccount(item)?.error_status;
|
||||||
|
return `异常(${errorStatusMap.get(error_status)?.text ?? ''})`;
|
||||||
|
};
|
||||||
|
const handleCancel = async (item) => {
|
||||||
|
const error_status = getSyncMediaAccount(item)?.error_status;
|
||||||
|
await deleteSyncStatus(item.id);
|
||||||
|
|
||||||
const onBtnClick = (item) => {
|
item.status = EnumStatus.ABNORMAL;
|
||||||
if (isUnauthorizedStatus(item.status)) {
|
item.error_status = error_status;
|
||||||
|
|
||||||
|
emits('updateSyncStatus', item);
|
||||||
|
};
|
||||||
|
const showConfirmBtn = (item) => {
|
||||||
|
const error_status = getSyncMediaAccount(item)?.error_status;
|
||||||
|
return [EnumErrorStatus.MISSING, EnumErrorStatus.LOGIN].includes(error_status);
|
||||||
|
};
|
||||||
|
const handleConfirm = (item) => {
|
||||||
|
const error_status = getSyncMediaAccount(item)?.error_status;
|
||||||
|
if (error_status === EnumErrorStatus.MISSING) {
|
||||||
|
syncData(item);
|
||||||
|
}
|
||||||
|
if (error_status === EnumErrorStatus.LOGIN) {
|
||||||
handleReauthorize(item);
|
handleReauthorize(item);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([EnumStatus.PAUSE, EnumStatus.NORMAL].includes(item.status) || isAbnormalStatus(item.status)) {
|
|
||||||
handleReauthorize(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePause(item);
|
|
||||||
};
|
};
|
||||||
|
const getConfirmBtnText = (item) => {
|
||||||
const getBtnText = (item) => {
|
const error_status = getSyncMediaAccount(item)?.error_status;
|
||||||
if (isUnauthorizedStatus(item.status)) {
|
if (error_status === EnumErrorStatus.MISSING) {
|
||||||
return '去授权';
|
return '重新更新';
|
||||||
}
|
}
|
||||||
|
if (error_status === EnumErrorStatus.LOGIN) {
|
||||||
if ([EnumStatus.PAUSE, EnumStatus.NORMAL].includes(item.status) || isAbnormalStatus(item.status)) {
|
|
||||||
return '重新授权';
|
return '重新授权';
|
||||||
}
|
}
|
||||||
|
|
||||||
return '暂停同步';
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
<span>确认暂停同步 “{{ accountName }}” 这个账号的数据吗?</span>
|
<span>确认暂停同步 “{{ accountName }}” 这个账号的数据吗?</span>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<a-button class="cancel-btn" size="large" @click="onClose">取消</a-button>
|
<a-button size="large" @click="onClose">取消</a-button>
|
||||||
<a-button type="primary" class="ml-16px" size="large" @click="onConfirm">确定</a-button>
|
<a-button type="primary" class="ml-16px" size="large" @click="onConfirm">确定</a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|||||||
@ -1,9 +1,3 @@
|
|||||||
@mixin ellipsis {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-container {
|
.card-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -17,6 +11,7 @@
|
|||||||
padding: 12px 16px 16px;
|
padding: 12px 16px 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
position: relative;
|
||||||
.name {
|
.name {
|
||||||
color: var(--Text-1, #211f24);
|
color: var(--Text-1, #211f24);
|
||||||
font-family: $font-family-medium;
|
font-family: $font-family-medium;
|
||||||
@ -24,6 +19,7 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
margin-bottom: 11px;
|
margin-bottom: 11px;
|
||||||
|
width: fit-content;
|
||||||
// line-height: 22px; /* 157.143% */
|
// line-height: 22px; /* 157.143% */
|
||||||
}
|
}
|
||||||
.label {
|
.label {
|
||||||
@ -78,6 +74,23 @@
|
|||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
transition: opacity 0.1s cubic-bezier(0, 0, 1, 1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
.name {
|
||||||
|
font-family: $font-family-regular;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.operate-row {
|
.operate-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -86,7 +99,7 @@
|
|||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
}
|
}
|
||||||
&.checked {
|
&.checked {
|
||||||
border: 1px solid var(--Brand-6, #6D4CFE);
|
border: 1px solid var(--Brand-6, #6d4cfe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,176 +2,32 @@
|
|||||||
* @Author: RenXiaoDong
|
* @Author: RenXiaoDong
|
||||||
* @Date: 2025-06-25 17:51:46
|
* @Date: 2025-06-25 17:51:46
|
||||||
-->
|
-->
|
||||||
<template>
|
<script lang="jsx">
|
||||||
<a-modal
|
import { ref, computed } from 'vue';
|
||||||
v-model:visible="visible"
|
import {
|
||||||
:title="isEdit ? '编辑账号' : '添加账号'"
|
Modal,
|
||||||
modal-class="add-account-modal"
|
Form,
|
||||||
width="500px"
|
FormItem,
|
||||||
:mask-closable="false"
|
Input,
|
||||||
@close="onClose"
|
RadioGroup,
|
||||||
>
|
Radio,
|
||||||
<a-form ref="formRef" :model="form" :rules="rules" layout="horizontal" auto-label-width>
|
Upload,
|
||||||
<a-form-item v-if="!isEdit" label="上传方式" required>
|
Button,
|
||||||
<a-radio-group v-model="uploadType">
|
Switch,
|
||||||
<a-radio value="manual">手动添加账号</a-radio>
|
Tooltip,
|
||||||
<a-radio value="batch">批量导入账号</a-radio>
|
Notification,
|
||||||
</a-radio-group>
|
Message as AMessage,
|
||||||
</a-form-item>
|
Textarea,
|
||||||
|
} from '@arco-design/web-vue';
|
||||||
<!-- 批量导入账号模式下的内容 -->
|
|
||||||
<template v-if="isBatchImport">
|
|
||||||
<a-form-item label="账户文件" required>
|
|
||||||
<!-- 默认状态 -->
|
|
||||||
<div class="upload-block">
|
|
||||||
<template v-if="uploadStatus === UploadStatus.DEFAULT">
|
|
||||||
<a-upload
|
|
||||||
ref="uploadRef"
|
|
||||||
action="/"
|
|
||||||
draggable
|
|
||||||
:custom-request="handleUpload"
|
|
||||||
accept=".xlsx,.xls"
|
|
||||||
:show-file-list="false"
|
|
||||||
>
|
|
||||||
<template #upload-button>
|
|
||||||
<div class="upload-box">
|
|
||||||
<icon-plus size="14" class="mb-16px" />
|
|
||||||
<span class="text mb-4px">点击或拖拽文件到此处上传</span>
|
|
||||||
<span class="tip">支持 xls, xlsx格式</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</a-upload>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div
|
|
||||||
class="flex items-center justify-between py-9px px-12px flex-1 import-row"
|
|
||||||
:class="{
|
|
||||||
error: uploadStatus === UploadStatus.ERROR,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<icon-file size="16" class="flex-shrink-0" />
|
|
||||||
<span class="name ml-8px">{{ fileName }}</span>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
v-if="uploadStatus === UploadStatus.ERROR"
|
|
||||||
class="upload-error flex-shrink-0"
|
|
||||||
@click="handleBatchImport"
|
|
||||||
>
|
|
||||||
点击重试
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<img :src="icon2" width="16" height="16" class="cursor-pointer ml-12px" @click="removeFile" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div class="flex items-center cursor-pointer mt-8px" @click="handleDownloadTemplate">
|
|
||||||
<img :src="icon1" width="16" height="16" class="mr-4px" />
|
|
||||||
<span class="dt">下载账户导入模板.xlsx</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 手动添加账号 -->
|
|
||||||
<template v-else>
|
|
||||||
<template v-if="isEdit">
|
|
||||||
<a-form-item label="账号名称" field="name">
|
|
||||||
<a-input v-model="form.name" placeholder="请输入..." size="large" disabled />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="账号ID" field="account_id">
|
|
||||||
<a-input v-model="form.account_id" placeholder="请输入..." size="large" disabled />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="状态" field="status">
|
|
||||||
<StatusBox :status="form.status" />
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<a-form-item label="手机号码" field="mobile" required>
|
|
||||||
<a-input v-model="form.mobile" placeholder="请输入..." size="large" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="运营人员" field="operator_name" required>
|
|
||||||
<a-input v-model="form.operator_name" placeholder="请输入..." class="w-240px" size="large" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="运营平台" :required="!isEdit">
|
|
||||||
<img v-if="isEdit" :src="form.platform === 0 ? icon3 : icon4" width="24" height="24" />
|
|
||||||
<a-radio-group v-else v-model="form.platform">
|
|
||||||
<a-radio :value="0">抖音</a-radio>
|
|
||||||
<a-radio :value="1">小红书</a-radio>
|
|
||||||
</a-radio-group>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="号码持有人" field="holder_name">
|
|
||||||
<a-input v-model="form.holder_name" placeholder="请输入..." class="w-240px" size="large" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="选择分组">
|
|
||||||
<GroupSelect
|
|
||||||
v-model="form.group_id"
|
|
||||||
:multiple="false"
|
|
||||||
:options="groupOptions"
|
|
||||||
placeholder="请选择…"
|
|
||||||
size="large"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="选择标签">
|
|
||||||
<TagSelect v-model="form.tag_ids" :options="tagOptions" placeholder="请选择…" size="large" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="笔记链接" field="end_work_link">
|
|
||||||
<template #label>
|
|
||||||
<span class="label">笔记链接</span>
|
|
||||||
<a-tooltip content="平台将从该笔记“之后”的内容开始同步,该笔记及更早的数据均不采集">
|
|
||||||
<icon-question-circle size="14" class="ml-4px color-#737478" />
|
|
||||||
</a-tooltip>
|
|
||||||
</template>
|
|
||||||
<a-textarea
|
|
||||||
v-model="form.end_work_link"
|
|
||||||
placeholder="请输入..."
|
|
||||||
size="large"
|
|
||||||
max-length="72"
|
|
||||||
:auto-size="{ minRows: 3, maxRows: 5 }"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
<template v-if="!isEdit">
|
|
||||||
<a-form-item label="Cookie值">
|
|
||||||
<template #label>
|
|
||||||
<span class="label">Cookie值</span>
|
|
||||||
<a-tooltip content="开启后可直接填写 Cookie,无需扫码授权">
|
|
||||||
<icon-question-circle size="14" class="ml-4px color-#737478" />
|
|
||||||
</a-tooltip>
|
|
||||||
</template>
|
|
||||||
<a-switch v-model="isCustomCookie" size="large" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="isCustomCookie" label="" field="cookie">
|
|
||||||
<a-textarea
|
|
||||||
v-model="form.cookie"
|
|
||||||
placeholder="请输入..."
|
|
||||||
size="large"
|
|
||||||
max-length="72"
|
|
||||||
:auto-size="{ minRows: 3, maxRows: 5 }"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</a-form>
|
|
||||||
<template #footer>
|
|
||||||
<a-button size="large" class="cancel-btn" @click="onClose">取消</a-button>
|
|
||||||
<a-button type="primary" size="large" @click="onSubmit">
|
|
||||||
{{ confirmBtnText }}
|
|
||||||
</a-button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<AuthorizedAccountModal ref="authorizedAccountModalRef" @update="emits('update')" />
|
|
||||||
<ImportPromptModal ref="importPromptModalRef" />
|
|
||||||
</a-modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, reactive, onMounted } from 'vue';
|
|
||||||
import TagSelect from '@/views/property-marketing/media-account/components/tag-select';
|
|
||||||
import GroupSelect from '@/views/property-marketing/media-account/components/group-select';
|
|
||||||
import AuthorizedAccountModal from '../authorized-account-modal';
|
import AuthorizedAccountModal from '../authorized-account-modal';
|
||||||
import ImportPromptModal from '../import-prompt-modal';
|
// import ImportPromptModal from '../import-prompt-modal';
|
||||||
import StatusBox from '../status-box';
|
import StatusBox from '@/views/property-marketing/media-account/components/status-select/status-box.tsx';
|
||||||
|
import SyncDataModal from '../sync-data-modal';
|
||||||
|
import CommonSelect from '@/components/common-select';
|
||||||
|
|
||||||
|
// import { downloadByUrl } from '@/utils/tools';
|
||||||
|
import { showExportNotification } from '@/utils/arcoD';
|
||||||
|
import { genRandomId } from '@/utils/tools';
|
||||||
import {
|
import {
|
||||||
fetchAccountTags,
|
fetchAccountTags,
|
||||||
fetchAccountGroups,
|
fetchAccountGroups,
|
||||||
@ -180,14 +36,15 @@ import {
|
|||||||
putMediaAccounts,
|
putMediaAccounts,
|
||||||
getTemplateUrl,
|
getTemplateUrl,
|
||||||
batchMediaAccounts,
|
batchMediaAccounts,
|
||||||
|
getProjectList,
|
||||||
} from '@/api/all/propertyMarketing';
|
} from '@/api/all/propertyMarketing';
|
||||||
|
|
||||||
import icon1 from '@/assets/img/media-account/icon-download.png';
|
import icon1 from '@/assets/img/media-account/icon-download.png';
|
||||||
import icon2 from '@/assets/img/media-account/icon-delete.png';
|
import icon2 from '@/assets/img/media-account/icon-delete.png';
|
||||||
import icon3 from '@/assets/img/media-account/icon-dy.png';
|
import icon3 from '@/assets/img/media-account/icon-dy.png';
|
||||||
import icon4 from '@/assets/img/media-account/icon-xhs.png';
|
import icon4 from '@/assets/img/media-account/icon-xhs.png';
|
||||||
|
// import icon5 from '@/assets/img/media-account/icon-warn-1.png';
|
||||||
const emits = defineEmits(['update']);
|
// import icon6 from '@/assets/img/media-account/icon-success.png';
|
||||||
|
|
||||||
const UploadStatus = {
|
const UploadStatus = {
|
||||||
DEFAULT: 'default',
|
DEFAULT: 'default',
|
||||||
@ -198,15 +55,19 @@ const INITIAL_FORM = {
|
|||||||
mobile: '',
|
mobile: '',
|
||||||
operator_name: '',
|
operator_name: '',
|
||||||
holder_name: '',
|
holder_name: '',
|
||||||
platform: 0,
|
platform: 1,
|
||||||
group_id: undefined,
|
group_id: undefined,
|
||||||
tag_ids: [],
|
tag_ids: [],
|
||||||
|
project_ids: [],
|
||||||
end_work_link: undefined,
|
end_work_link: undefined,
|
||||||
cookie: undefined,
|
cookie: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(props, { emit, expose }) {
|
||||||
const groupOptions = ref([]);
|
const groupOptions = ref([]);
|
||||||
const tagOptions = ref([]);
|
const tagOptions = ref([]);
|
||||||
|
const projects = ref([]);
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
const uploadType = ref('manual');
|
const uploadType = ref('manual');
|
||||||
const uploadStatus = ref(UploadStatus.DEFAULT);
|
const uploadStatus = ref(UploadStatus.DEFAULT);
|
||||||
@ -216,10 +77,12 @@ const fileName = ref('');
|
|||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
const file = ref(null);
|
const file = ref(null);
|
||||||
const authorizedAccountModalRef = ref(null);
|
const authorizedAccountModalRef = ref(null);
|
||||||
const importPromptModalRef = ref(null);
|
// const importPromptModalRef = ref(null);
|
||||||
const uploadRef = ref(null);
|
const uploadRef = ref(null);
|
||||||
const isCustomCookie = ref(false);
|
const isCustomCookie = ref(false);
|
||||||
const form = ref(cloneDeep(INITIAL_FORM));
|
const form = ref(cloneDeep(INITIAL_FORM));
|
||||||
|
const syncDataModalRef = ref(null);
|
||||||
|
const importLoading = ref(false);
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
mobile: [
|
mobile: [
|
||||||
@ -240,54 +103,60 @@ const rules = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
operator_name: [{ required: true, message: '请输入运营人员' }],
|
operator_name: [{ required: true, message: '请输入运营人员' }],
|
||||||
// holder_name: [{ required: true, message: '请输入号码持有人' }],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const isBatchImport = computed(() => uploadType.value === 'batch');
|
const isBatchImport = computed(() => uploadType.value === 'batch');
|
||||||
const confirmBtnText = computed(() => {
|
const confirmBtnText = computed(() => {
|
||||||
if (isBatchImport.value) return '确定导入';
|
if (isBatchImport.value) {
|
||||||
return isEdit.value ? '确定' : '生成授权码';
|
return importLoading.value ? '导入中' : '确定导入';
|
||||||
|
} else if (isEdit.value) {
|
||||||
|
return '确定';
|
||||||
|
} else {
|
||||||
|
return isCustomCookie.value ? '确认添加' : '生成授权码';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取分组数据
|
|
||||||
const getGroups = async () => {
|
const getGroups = async () => {
|
||||||
const { code, data } = await fetchAccountGroups();
|
const { code, data } = await fetchAccountGroups();
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
groupOptions.value = data;
|
groupOptions.value = data;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取标签数据
|
|
||||||
const getTags = async () => {
|
const getTags = async () => {
|
||||||
const { code, data } = await fetchAccountTags();
|
const { code, data } = await fetchAccountTags();
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
tagOptions.value = data;
|
tagOptions.value = data;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const getProjects = async () => {
|
||||||
|
const { code, data } = await getProjectList();
|
||||||
|
if (code === 200) {
|
||||||
|
projects.value = data;
|
||||||
|
}
|
||||||
|
};
|
||||||
function handleUpload(option) {
|
function handleUpload(option) {
|
||||||
const { fileItem } = option;
|
const { fileItem } = option;
|
||||||
|
|
||||||
uploadStatus.value = UploadStatus.WAITING;
|
uploadStatus.value = UploadStatus.WAITING;
|
||||||
|
|
||||||
file.value = fileItem.file;
|
file.value = fileItem.file;
|
||||||
fileName.value = fileItem.name;
|
fileName.value = fileItem.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeFile() {
|
function removeFile() {
|
||||||
fileName.value = '';
|
fileName.value = '';
|
||||||
file.value = null;
|
file.value = null;
|
||||||
|
importLoading.value = false;
|
||||||
uploadStatus.value = UploadStatus.DEFAULT;
|
uploadStatus.value = UploadStatus.DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
formRef.value.resetFields();
|
formRef.value?.resetFields();
|
||||||
formRef.value.clearValidate();
|
formRef.value?.clearValidate();
|
||||||
|
groupOptions.value = [];
|
||||||
|
tagOptions.value = [];
|
||||||
|
projects.value = [];
|
||||||
form.value = cloneDeep(INITIAL_FORM);
|
form.value = cloneDeep(INITIAL_FORM);
|
||||||
fileName.value = '';
|
fileName.value = '';
|
||||||
file.value = null;
|
file.value = null;
|
||||||
isEdit.value = false;
|
isEdit.value = false;
|
||||||
|
importLoading.value = false;
|
||||||
isCustomCookie.value = false;
|
isCustomCookie.value = false;
|
||||||
uploadStatus.value = UploadStatus.DEFAULT;
|
uploadStatus.value = UploadStatus.DEFAULT;
|
||||||
uploadType.value = 'manual';
|
uploadType.value = 'manual';
|
||||||
@ -296,20 +165,17 @@ const onClose = () => {
|
|||||||
reset();
|
reset();
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const open = (accountId = '') => {
|
const open = (accountId = '') => {
|
||||||
id.value = accountId;
|
id.value = accountId;
|
||||||
isEdit.value = !!accountId;
|
isEdit.value = !!accountId;
|
||||||
|
|
||||||
if (accountId) {
|
if (accountId) {
|
||||||
getAccountDetail();
|
getAccountDetail();
|
||||||
}
|
}
|
||||||
getGroups();
|
getGroups();
|
||||||
getTags();
|
getTags();
|
||||||
|
getProjects();
|
||||||
visible.value = true;
|
visible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAccountDetail = async () => {
|
const getAccountDetail = async () => {
|
||||||
const { code, data } = await getMediaAccountsDetail(id.value);
|
const { code, data } = await getMediaAccountsDetail(id.value);
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
@ -319,30 +185,35 @@ const getAccountDetail = async () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBatchImport = async () => {
|
const handleBatchImport = async () => {
|
||||||
try {
|
try {
|
||||||
|
if (!file.value) {
|
||||||
|
AMessage.warning('请上传要导入的文件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
importLoading.value = true;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file.value);
|
formData.append('file', file.value);
|
||||||
|
const { code, data } = await batchMediaAccounts(formData, {
|
||||||
const { code } = await batchMediaAccounts(formData, {
|
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data',
|
'Content-Type': 'multipart/form-data',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
AMessage.success('导入成功');
|
const id = genRandomId();
|
||||||
emits('update');
|
showExportNotification(`正在导入“${file.value.name}”,请稍后...`, { id });
|
||||||
|
emit('startQueryTaskStatus', data.id, id);
|
||||||
onClose();
|
onClose();
|
||||||
importPromptModalRef.value.open();
|
|
||||||
} else {
|
} else {
|
||||||
uploadStatus.value = UploadStatus.ERROR;
|
uploadStatus.value = UploadStatus.ERROR;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
uploadStatus.value = UploadStatus.ERROR;
|
uploadStatus.value = UploadStatus.ERROR;
|
||||||
|
} finally {
|
||||||
|
importLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddAccount = async () => {
|
const handleAddAccount = async () => {
|
||||||
const _isCustomCookie = isCustomCookie.value;
|
const _isCustomCookie = isCustomCookie.value;
|
||||||
const { code, data } = await postMediaAccounts({
|
const { code, data } = await postMediaAccounts({
|
||||||
@ -350,49 +221,234 @@ const handleAddAccount = async () => {
|
|||||||
cookie: _isCustomCookie ? form.value.cookie : undefined,
|
cookie: _isCustomCookie ? form.value.cookie : undefined,
|
||||||
});
|
});
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
emits('update');
|
emit('update');
|
||||||
onClose();
|
onClose();
|
||||||
|
const { id: _id, platform } = data;
|
||||||
const { id, platform } = data;
|
if (_isCustomCookie) {
|
||||||
!_isCustomCookie && startAuthorized(id, platform);
|
syncDataModalRef.value.open(_id);
|
||||||
|
} else {
|
||||||
|
startAuthorized(_id, platform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditAccount = async () => {
|
const handleEditAccount = async () => {
|
||||||
const { code } = await putMediaAccounts({ id: id.value, ...form.value });
|
const { code } = await putMediaAccounts({ id: id.value, ...form.value });
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
AMessage.success('修改成功');
|
AMessage.success('修改成功');
|
||||||
emits('update');
|
emit('update');
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function onSubmit() {
|
async function onSubmit() {
|
||||||
if (isBatchImport.value) {
|
if (isBatchImport.value) {
|
||||||
handleBatchImport();
|
handleBatchImport();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
formRef.value.validate(async (errors) => {
|
formRef.value.validate(async (errors) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
|
if (isCustomCookie.value && !form.value.cookie) {
|
||||||
|
AMessage.warning('请填写Cookie值');
|
||||||
|
return;
|
||||||
|
}
|
||||||
isEdit.value ? handleEditAccount() : handleAddAccount();
|
isEdit.value ? handleEditAccount() : handleAddAccount();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const startAuthorized = (id, platform) => {
|
const startAuthorized = (id, platform) => {
|
||||||
authorizedAccountModalRef.value.open(id, platform);
|
authorizedAccountModalRef.value.open(id, platform);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownloadTemplate = async () => {
|
const handleDownloadTemplate = async () => {
|
||||||
const { code, data } = await getTemplateUrl();
|
const { code, data } = await getTemplateUrl();
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
window.open(data.download_url, '_blank');
|
window.open(data.download_url, '_blank');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const renderLabel = (label, tooltipContent) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span class="label">{label}</span>
|
||||||
|
<Tooltip content={tooltipContent}>
|
||||||
|
<icon-question-circle size="14" class="ml-4px color-#737478" />
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// 对外暴露打开弹窗方法
|
expose({ open });
|
||||||
defineExpose({ open });
|
|
||||||
|
return () => (
|
||||||
|
<Modal
|
||||||
|
v-model:visible={visible.value}
|
||||||
|
title={isEdit.value ? '编辑账号' : '添加账号'}
|
||||||
|
modal-class="add-account-modal"
|
||||||
|
width="500px"
|
||||||
|
mask-closable={false}
|
||||||
|
onClose={onClose}
|
||||||
|
footer={null}
|
||||||
|
>
|
||||||
|
<Form ref={formRef} model={form.value} rules={rules} layout="horizontal" auto-label-width>
|
||||||
|
{!isEdit.value && (
|
||||||
|
<FormItem label="上传方式" required>
|
||||||
|
<RadioGroup v-model={uploadType.value}>
|
||||||
|
<Radio value="manual">手动添加账号</Radio>
|
||||||
|
<Radio value="batch">批量导入账号</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
{isBatchImport.value ? (
|
||||||
|
<FormItem label="账号文件" required>
|
||||||
|
<div class="upload-block">
|
||||||
|
{uploadStatus.value === UploadStatus.DEFAULT ? (
|
||||||
|
<Upload
|
||||||
|
ref={uploadRef}
|
||||||
|
action="/"
|
||||||
|
draggable
|
||||||
|
custom-request={handleUpload}
|
||||||
|
accept=".xlsx,.xls"
|
||||||
|
show-file-list={false}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
'upload-button': () => (
|
||||||
|
<div class="upload-box">
|
||||||
|
<span class="text mb-4px">点击或拖拽文件到此处上传</span>
|
||||||
|
<span class="tip">支持 xls, xlsx格式</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
</Upload>
|
||||||
|
) : (
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
'flex items-center justify-between py-9px px-12px flex-1 import-row',
|
||||||
|
{ error: uploadStatus.value === UploadStatus.ERROR },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="name ml-8px">{fileName.value}</span>
|
||||||
|
</div>
|
||||||
|
{uploadStatus.value === UploadStatus.ERROR && (
|
||||||
|
<span class="upload-error flex-shrink-0" onClick={handleBatchImport}>
|
||||||
|
点击重试
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<img src={icon2} width="16" height="16" class="cursor-pointer ml-12px" onClick={removeFile} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div class="flex items-center cursor-pointer mt-8px" onClick={handleDownloadTemplate}>
|
||||||
|
<img src={icon1} width="16" height="16" class="mr-4px" />
|
||||||
|
<span class="dt">下载账户导入模板.xlsx</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{isEdit.value && (
|
||||||
|
<>
|
||||||
|
<FormItem label="账号名称" field="name">
|
||||||
|
<Input v-model={form.value.name} placeholder="请输入..." size="large" disabled />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="账号ID" field="account_id">
|
||||||
|
<Input v-model={form.value.account_id} placeholder="请输入..." size="large" disabled />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="状态" field="status">
|
||||||
|
<StatusBox item={form.value} />
|
||||||
|
</FormItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<FormItem label="手机号码" field="mobile" required>
|
||||||
|
<Input v-model={form.value.mobile} placeholder="请输入..." size="large" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="运营人员" field="operator_name" required>
|
||||||
|
<Input v-model={form.value.operator_name} placeholder="请输入..." class="w-240px" size="large" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="运营平台" required={!isEdit.value}>
|
||||||
|
{isEdit.value ? (
|
||||||
|
<img src={form.value.platform === 0 ? icon3 : icon4} width="24" height="24" />
|
||||||
|
) : (
|
||||||
|
<RadioGroup v-model={form.value.platform}>
|
||||||
|
<Radio value={1}>小红书</Radio>
|
||||||
|
<Radio value={0}>抖音</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
)}
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="号码持有人" field="holder_name">
|
||||||
|
<Input v-model={form.value.holder_name} placeholder="请输入..." class="w-240px" size="large" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="所属项目">
|
||||||
|
<CommonSelect
|
||||||
|
v-model={form.value.project_ids}
|
||||||
|
options={projects.value}
|
||||||
|
placeholder="请选择…"
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="选择分组">
|
||||||
|
<CommonSelect
|
||||||
|
v-model={form.value.group_id}
|
||||||
|
multiple={false}
|
||||||
|
options={groupOptions.value}
|
||||||
|
placeholder="请选择…"
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem label="选择标签">
|
||||||
|
<CommonSelect v-model={form.value.tag_ids} options={tagOptions.value} placeholder="请选择…" size="large" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
label="笔记链接"
|
||||||
|
field="end_work_link"
|
||||||
|
v-slots={{
|
||||||
|
label: () =>
|
||||||
|
renderLabel('笔记链接', '平台将从该笔记“之后”的内容开始同步,该笔记及更早的数据均不采集'),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Textarea
|
||||||
|
v-model={form.value.end_work_link}
|
||||||
|
placeholder="请输入..."
|
||||||
|
size="large"
|
||||||
|
auto-size={{ minRows: 3, maxRows: 5 }}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
label="Cookie值"
|
||||||
|
v-slots={{
|
||||||
|
label: () => renderLabel('Cookie值', 'Cookie,无需扫码授权'),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Switch v-model={isCustomCookie.value} size="large" />
|
||||||
|
</FormItem>
|
||||||
|
{isCustomCookie.value && (
|
||||||
|
<FormItem label="" field="cookie">
|
||||||
|
<Textarea
|
||||||
|
v-model={form.value.cookie}
|
||||||
|
placeholder="请输入..."
|
||||||
|
size="large"
|
||||||
|
auto-size={{ minRows: 5, maxRows: 8 }}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Form>
|
||||||
|
<div style="display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px;">
|
||||||
|
<Button size="large" onClick={onClose}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" size="large" loading={importLoading.value} onClick={onSubmit}>
|
||||||
|
{confirmBtnText.value}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AuthorizedAccountModal ref={authorizedAccountModalRef} onUpdate={() => emit('update')} />
|
||||||
|
{/* <ImportPromptModal ref={importPromptModalRef} /> */}
|
||||||
|
<SyncDataModal ref={syncDataModalRef} />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||