Commit c56852a3 by 王立鹏

Merge branch 'feature/17679-企业文化平台搭建' into 'master'

Feature/17679 企业文化平台搭建

See merge request !3
parents 494a2f6b efdbf410
{
"globals": {
"Component": true,
"ComponentPublicInstance": true,
"ComputedRef": true,
"DirectiveBinding": true,
"EffectScope": true,
"ElMessage": true,
"ElMessageBox": true,
"ExtractDefaultPropTypes": true,
"ExtractPropTypes": true,
"ExtractPublicPropTypes": true,
"InjectionKey": true,
"MaybeRef": true,
"MaybeRefOrGetter": true,
"PropType": true,
"Ref": true,
"ShallowRef": true,
"Slot": true,
"Slots": true,
"VNode": true,
"WritableComputedRef": true,
"computed": true,
"createApp": true,
"customRef": true,
"defineAsyncComponent": true,
"defineComponent": true,
"effectScope": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"getCurrentWatcher": true,
"h": true,
"inject": true,
"isProxy": true,
"isReactive": true,
"isReadonly": true,
"isRef": true,
"isShallow": true,
"markRaw": true,
"nextTick": true,
"onActivated": true,
"onBeforeMount": true,
"onBeforeRouteLeave": true,
"onBeforeRouteUpdate": true,
"onBeforeUnmount": true,
"onBeforeUpdate": true,
"onDeactivated": true,
"onErrorCaptured": true,
"onMounted": true,
"onRenderTracked": true,
"onRenderTriggered": true,
"onScopeDispose": true,
"onServerPrefetch": true,
"onUnmounted": true,
"onUpdated": true,
"onWatcherCleanup": true,
"provide": true,
"reactive": true,
"readonly": true,
"ref": true,
"resolveComponent": true,
"shallowReactive": true,
"shallowReadonly": true,
"shallowRef": true,
"toRaw": true,
"toRef": true,
"toRefs": true,
"toValue": true,
"triggerRef": true,
"unref": true,
"useAttrs": true,
"useCssModule": true,
"useCssVars": true,
"useId": true,
"useLink": true,
"useModel": true,
"useRoute": true,
"useRouter": true,
"useSlots": true,
"useTemplateRef": true,
"watch": true,
"watchEffect": true,
"watchPostEffect": true,
"watchSyncEffect": true
}
}
...@@ -34,3 +34,11 @@ coverage ...@@ -34,3 +34,11 @@ coverage
# Vitest # Vitest
__screenshots__/ __screenshots__/
# 自动导入生成的文件
auto-imports.d.ts
components.d.ts
.eslintrc-auto-import.json
# 打包报告
stats.html
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
const EffectScope: typeof import('vue').EffectScope
const ElMessage: typeof import('element-plus/es').ElMessage
const ElMessageBox: typeof import('element-plus/es').ElMessageBox
const ElSwitch: typeof import('element-plus/es').ElSwitch
const computed: typeof import('vue').computed
const createApp: typeof import('vue').createApp
const customRef: typeof import('vue').customRef
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
const defineComponent: typeof import('vue').defineComponent
const effectScope: typeof import('vue').effectScope
const getCurrentInstance: typeof import('vue').getCurrentInstance
const getCurrentScope: typeof import('vue').getCurrentScope
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
const h: typeof import('vue').h
const inject: typeof import('vue').inject
const isProxy: typeof import('vue').isProxy
const isReactive: typeof import('vue').isReactive
const isReadonly: typeof import('vue').isReadonly
const isRef: typeof import('vue').isRef
const isShallow: typeof import('vue').isShallow
const markRaw: typeof import('vue').markRaw
const nextTick: typeof import('vue').nextTick
const onActivated: typeof import('vue').onActivated
const onBeforeMount: typeof import('vue').onBeforeMount
const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave
const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
const onDeactivated: typeof import('vue').onDeactivated
const onErrorCaptured: typeof import('vue').onErrorCaptured
const onMounted: typeof import('vue').onMounted
const onRenderTracked: typeof import('vue').onRenderTracked
const onRenderTriggered: typeof import('vue').onRenderTriggered
const onScopeDispose: typeof import('vue').onScopeDispose
const onServerPrefetch: typeof import('vue').onServerPrefetch
const onUnmounted: typeof import('vue').onUnmounted
const onUpdated: typeof import('vue').onUpdated
const onWatcherCleanup: typeof import('vue').onWatcherCleanup
const provide: typeof import('vue').provide
const reactive: typeof import('vue').reactive
const readonly: typeof import('vue').readonly
const ref: typeof import('vue').ref
const resolveComponent: typeof import('vue').resolveComponent
const shallowReactive: typeof import('vue').shallowReactive
const shallowReadonly: typeof import('vue').shallowReadonly
const shallowRef: typeof import('vue').shallowRef
const toRaw: typeof import('vue').toRaw
const toRef: typeof import('vue').toRef
const toRefs: typeof import('vue').toRefs
const toValue: typeof import('vue').toValue
const triggerRef: typeof import('vue').triggerRef
const unref: typeof import('vue').unref
const useAttrs: typeof import('vue').useAttrs
const useCssModule: typeof import('vue').useCssModule
const useCssVars: typeof import('vue').useCssVars
const useId: typeof import('vue').useId
const useLink: typeof import('vue-router').useLink
const useModel: typeof import('vue').useModel
const useRoute: typeof import('vue-router').useRoute
const useRouter: typeof import('vue-router').useRouter
const useSlots: typeof import('vue').useSlots
const useTemplateRef: typeof import('vue').useTemplateRef
const watch: typeof import('vue').watch
const watchEffect: typeof import('vue').watchEffect
const watchPostEffect: typeof import('vue').watchPostEffect
const watchSyncEffect: typeof import('vue').watchSyncEffect
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}
/* eslint-disable */
// @ts-nocheck
// biome-ignore lint: disable
// oxlint-disable
// ------
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
import { GlobalComponents } from 'vue'
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
ArticleContent: typeof import('./src/components/common/ArticleContent/index.vue')['default']
BaseButton: typeof import('./src/components/common/ElComponents/ElButton/BaseButton.vue')['default']
Comment: typeof import('./src/components/common/Comment/index.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSlider: typeof import('element-plus/es')['ElSlider']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElUpload: typeof import('element-plus/es')['ElUpload']
IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default']
IconDocumentation: typeof import('./src/components/icons/IconDocumentation.vue')['default']
IconEcosystem: typeof import('./src/components/icons/IconEcosystem.vue')['default']
IconSupport: typeof import('./src/components/icons/IconSupport.vue')['default']
IconTooling: typeof import('./src/components/icons/IconTooling.vue')['default']
LoadingComponent: typeof import('./src/components/common/LoadingComponent/index.vue')['default']
PublishBox: typeof import('./src/components/common/PublishBox/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SelectTags: typeof import('./src/components/common/SelectTags/index.vue')['default']
SelectTagsDialog: typeof import('./src/components/common/PublishBox/components/selectTagsDialog.vue')['default']
SvgIcon: typeof import('./src/components/common/SvgIcon/svgIcon.vue')['default']
UploadFile: typeof import('./src/components/common/UploadFile/index.vue')['default']
UploadVideo: typeof import('./src/components/common/UploadVideo/index.vue')['default']
}
export interface GlobalDirectives {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}
// For TSX support
declare global {
const ArticleContent: typeof import('./src/components/common/ArticleContent/index.vue')['default']
const BaseButton: typeof import('./src/components/common/ElComponents/ElButton/BaseButton.vue')['default']
const Comment: typeof import('./src/components/common/Comment/index.vue')['default']
const ElAlert: typeof import('element-plus/es')['ElAlert']
const ElAvatar: typeof import('element-plus/es')['ElAvatar']
const ElButton: typeof import('element-plus/es')['ElButton']
const ElCard: typeof import('element-plus/es')['ElCard']
const ElCarousel: typeof import('element-plus/es')['ElCarousel']
const ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
const ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
const ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
const ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
const ElDialog: typeof import('element-plus/es')['ElDialog']
const ElDivider: typeof import('element-plus/es')['ElDivider']
const ElDropdown: typeof import('element-plus/es')['ElDropdown']
const ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
const ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
const ElEmpty: typeof import('element-plus/es')['ElEmpty']
const ElForm: typeof import('element-plus/es')['ElForm']
const ElFormItem: typeof import('element-plus/es')['ElFormItem']
const ElIcon: typeof import('element-plus/es')['ElIcon']
const ElImage: typeof import('element-plus/es')['ElImage']
const ElInput: typeof import('element-plus/es')['ElInput']
const ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
const ElLink: typeof import('element-plus/es')['ElLink']
const ElMenu: typeof import('element-plus/es')['ElMenu']
const ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
const ElOption: typeof import('element-plus/es')['ElOption']
const ElPagination: typeof import('element-plus/es')['ElPagination']
const ElPopover: typeof import('element-plus/es')['ElPopover']
const ElProgress: typeof import('element-plus/es')['ElProgress']
const ElRadio: typeof import('element-plus/es')['ElRadio']
const ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
const ElSelect: typeof import('element-plus/es')['ElSelect']
const ElSlider: typeof import('element-plus/es')['ElSlider']
const ElSwitch: typeof import('element-plus/es')['ElSwitch']
const ElTable: typeof import('element-plus/es')['ElTable']
const ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
const ElTabPane: typeof import('element-plus/es')['ElTabPane']
const ElTabs: typeof import('element-plus/es')['ElTabs']
const ElTag: typeof import('element-plus/es')['ElTag']
const ElTooltip: typeof import('element-plus/es')['ElTooltip']
const ElUpload: typeof import('element-plus/es')['ElUpload']
const IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default']
const IconDocumentation: typeof import('./src/components/icons/IconDocumentation.vue')['default']
const IconEcosystem: typeof import('./src/components/icons/IconEcosystem.vue')['default']
const IconSupport: typeof import('./src/components/icons/IconSupport.vue')['default']
const IconTooling: typeof import('./src/components/icons/IconTooling.vue')['default']
const LoadingComponent: typeof import('./src/components/common/LoadingComponent/index.vue')['default']
const PublishBox: typeof import('./src/components/common/PublishBox/index.vue')['default']
const RouterLink: typeof import('vue-router')['RouterLink']
const RouterView: typeof import('vue-router')['RouterView']
const SelectTags: typeof import('./src/components/common/SelectTags/index.vue')['default']
const SelectTagsDialog: typeof import('./src/components/common/PublishBox/components/selectTagsDialog.vue')['default']
const SvgIcon: typeof import('./src/components/common/SvgIcon/svgIcon.vue')['default']
const UploadFile: typeof import('./src/components/common/UploadFile/index.vue')['default']
const UploadVideo: typeof import('./src/components/common/UploadVideo/index.vue')['default']
}
\ No newline at end of file
...@@ -3,6 +3,7 @@ import ssh from 'ssh2' ...@@ -3,6 +3,7 @@ import ssh from 'ssh2'
import archiver from 'archiver' import archiver from 'archiver'
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
import inquirer from 'inquirer'
const unzipDirMode = { const unzipDirMode = {
spawn: 'npm run build:prod', spawn: 'npm run build:prod',
...@@ -33,6 +34,13 @@ let conn = new ssh.Client() ...@@ -33,6 +34,13 @@ let conn = new ssh.Client()
async function start() { async function start() {
try { try {
if (process.argv.slice(2).includes('--update-info')) {
// 0. 获取更新信息
const updateInfo = await getUpdateInfo()
if (updateInfo.length) {
writeUpdateInfo(updateInfo)
}
}
// 1. 打包 // 1. 打包
await build() await build()
// 2. 压缩zip // 2. 压缩zip
...@@ -49,11 +57,44 @@ async function start() { ...@@ -49,11 +57,44 @@ async function start() {
// 5. 断开ssh,并删除本地压缩包 // 5. 断开ssh,并删除本地压缩包
conn.end() conn.end()
delZip() delZip()
delUpdateInfo()
} }
} }
start() start()
/** /**
* 0.获取更新信息
*/
function getUpdateInfo() {
return inquirer
.prompt([
{
type: 'input',
name: 'updateInfo',
message: '请输入更新信息换行请用空格带条',
default: '',
},
])
.then((res) => {
return res.updateInfo.trim()
})
}
function writeUpdateInfo(updateInfo) {
fs.writeFileSync(path.resolve(process.cwd(), 'pushUpdate.txt'), updateInfo)
}
function delUpdateInfo() {
try {
fs.unlinkSync(path.resolve(process.cwd(), 'pushUpdate.txt'))
} catch (err) {
if (err.code !== 'ENOENT') {
throw err
}
}
}
/**
* 1. 本地构建项目 * 1. 本地构建项目
*/ */
function build() { function build() {
...@@ -87,11 +128,13 @@ function startZip() { ...@@ -87,11 +128,13 @@ function startZip() {
gzip: true, // 如果需要压缩,可以使用 gzip gzip: true, // 如果需要压缩,可以使用 gzip
gzipOptions: { level: 9 }, // gzip 压缩级别 gzipOptions: { level: 9 }, // gzip 压缩级别
}).on('error', (err) => reject(err)) }).on('error', (err) => reject(err))
console.log('zipPath', zipPath) // console.log('zipPath', zipPath)
const output = fs.createWriteStream(zipPath) const output = fs.createWriteStream(zipPath)
//监听流的打包 //监听流的打包
output.on('close', (err) => { output.on('close', (err) => {
console.log('err', err) if (err) {
return console.error('打包失败', err)
}
console.log('目标打包完成') console.log('目标打包完成')
resolve(true) resolve(true)
}) })
......
/// <reference types="vite/client" /> /// <reference types="vite/client" />
declare const __CORE_LIB_VERSION__: {
vue: string
'vue-router': string
'element-plus': string
pinia: string
unocss: string
node: string
}
// 声明虚拟模块
declare module 'virtual:push-update' {
const updateInfo: string
export default updateInfo
}
// src/types/wangeditor.d.ts
declare module '@wangeditor/editor-for-vue' {
const Editor: any
const Toolbar: any
export { Editor, Toolbar }
}
...@@ -14,25 +14,32 @@ ...@@ -14,25 +14,32 @@
"lint": "eslint . --fix --cache", "lint": "eslint . --fix --cache",
"format": "prettier --write src/", "format": "prettier --write src/",
"build-only": "vite build", "build-only": "vite build",
"build:dev": "nvm use 20 && vite build --mode development",
"build:test": "nvm use 20 && vite build --mode test", "build:test": "nvm use 20 && vite build --mode test",
"deploy:test": "node deploy/deploytest.js", "deploy:test": "node deploy/deploytest.js",
"build:prod": "nvm use 20 && vite build --mode production", "build:prod": "nvm use 20 && vite build --mode production",
"deploy:prod": "node deploy/deployprod.js" "deploy:prod": "node deploy/deployprod.js",
"deploy:prod:update-info": "node deploy/deployprod.js --update-info"
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.2", "@element-plus/icons-vue": "^2.3.2",
"@vueuse/components": "^14.0.0", "@vueuse/components": "^14.0.0",
"@vueuse/core": "^14.0.0", "@vueuse/core": "^14.0.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"@wecom/jssdk": "^2.3.3",
"archiver": "^7.0.1", "archiver": "^7.0.1",
"axios": "^1.13.0", "axios": "^1.13.0",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"element-plus": "^2.11.5", "element-plus": "^2.11.5",
"inquirer": "^13.0.2",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"ssh2": "^1.17.0", "ssh2": "^1.17.0",
"vue": "^3.5.22", "vue": "^3.5.22",
"vue-router": "^4.6.3" "vue-router": "^4.6.3"
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/ep": "^1.2.3",
"@tsconfig/node22": "^22.0.2", "@tsconfig/node22": "^22.0.2",
"@types/node": "^22.18.11", "@types/node": "^22.18.11",
"@vitejs/plugin-vue": "^6.0.1", "@vitejs/plugin-vue": "^6.0.1",
...@@ -40,15 +47,18 @@ ...@@ -40,15 +47,18 @@
"@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.6.0", "@vue/eslint-config-typescript": "^14.6.0",
"@vue/tsconfig": "^0.8.1", "@vue/tsconfig": "^0.8.1",
"baseline-browser-mapping": "^2.9.14",
"eslint": "^9.37.0", "eslint": "^9.37.0",
"eslint-plugin-vue": "~10.5.0", "eslint-plugin-vue": "~10.5.0",
"jiti": "^2.6.1", "jiti": "^2.6.1",
"npm-run-all2": "^8.0.4", "npm-run-all2": "^8.0.4",
"prettier": "3.6.2", "prettier": "3.6.2",
"rollup-plugin-visualizer": "^6.0.5",
"sass-embedded": "^1.93.2", "sass-embedded": "^1.93.2",
"typescript": "~5.9.0", "typescript": "~5.9.0",
"unocss": "^66.5.4", "unocss": "^66.5.4",
"unplugin-auto-import": "^20.2.0", "unplugin-auto-import": "^20.2.0",
"unplugin-icons": "^22.5.0",
"unplugin-vue-components": "^30.0.0", "unplugin-vue-components": "^30.0.0",
"vite": "^7.1.11", "vite": "^7.1.11",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",
......
...@@ -7,10 +7,16 @@ ...@@ -7,10 +7,16 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import zhCn from 'element-plus/es/locale/lang/zh-cn' import zhCn from 'element-plus/es/locale/lang/zh-cn'
import { useUserStore } from '@/stores/user' import { initWxConfig } from '@/utils/wxUtil/initWXConfig'
const locale = ref(zhCn) const locale = ref(zhCn)
// const userStore = useUserStore()
// userStore.fetchUserInfo().then((res) => { onMounted(() => {
// console.log(res) console.table(__CORE_LIB_VERSION__)
// }) if (import.meta.env.MODE === 'production') {
setTimeout(() => {
initWxConfig()
}, 3000)
}
})
</script> </script>
...@@ -10,6 +10,13 @@ import type { ...@@ -10,6 +10,13 @@ import type {
CommentSearchParams, CommentSearchParams,
InterviewItemDto, InterviewItemDto,
ColumnItemDto, ColumnItemDto,
VideoOptionDto,
CommentChildrenSearchParams,
SearchMoreColumnParams,
SearchMoreColumnItemDto,
SearchMoreVideoParams,
SearchMoreVideoItemDto,
SecondCommentItemDto,
} from './types' } from './types'
import type { BackendServicePageResult, PageSearchParams } from '@/utils/request/types' import type { BackendServicePageResult, PageSearchParams } from '@/utils/request/types'
...@@ -82,6 +89,16 @@ export const getColumnOptions = () => { ...@@ -82,6 +89,16 @@ export const getColumnOptions = () => {
} }
/** /**
* 获取视频栏目列表 --不分页 用户新增的时候传
*/
export const getVideoOptions = () => {
return service.request<VideoOptionDto[]>({
url: '/api/cultureColumn/listNoPage?type=video',
method: 'POST',
})
}
/**
* 获取首页专栏列表list —— 分页 * 获取首页专栏列表list —— 分页
*/ */
export const getColumnList = (data: PageSearchParams) => { export const getColumnList = (data: PageSearchParams) => {
...@@ -96,6 +113,19 @@ export const getColumnList = (data: PageSearchParams) => { ...@@ -96,6 +113,19 @@ export const getColumnList = (data: PageSearchParams) => {
} }
/** /**
* 获取专栏列表查看更多
*/
export const getColumnListViewMore = (data: SearchMoreColumnParams) => {
return service.request<BackendServicePageResult<SearchMoreColumnItemDto>>({
url: '/api/yaCulture/viewMore',
method: 'POST',
data: {
type: 'column',
...data,
},
})
}
/**
* 获取首页视频列表list —— 分页 * 获取首页视频列表list —— 分页
*/ */
export const getVideoList = (data: PageSearchParams) => { export const getVideoList = (data: PageSearchParams) => {
...@@ -110,6 +140,19 @@ export const getVideoList = (data: PageSearchParams) => { ...@@ -110,6 +140,19 @@ export const getVideoList = (data: PageSearchParams) => {
} }
/** /**
* 获取视频列表查看更多
*/
export const getVideoListViewMore = (data: SearchMoreVideoParams) => {
return service.request<BackendServicePageResult<SearchMoreVideoItemDto>>({
url: '/api/yaCulture/viewMore',
method: 'POST',
data: {
type: 'video',
...data,
},
})
}
/**
* 点赞或者取消点赞文章 * 点赞或者取消点赞文章
*/ */
export const addOrCanceArticlelLike = (articleId: number | string) => { export const addOrCanceArticlelLike = (articleId: number | string) => {
...@@ -206,3 +249,108 @@ export const addOrCancelArticleReward = (data: { articleId: number; ayabi: numbe ...@@ -206,3 +249,108 @@ export const addOrCancelArticleReward = (data: { articleId: number; ayabi: numbe
data, data,
}) })
} }
/**
* 给视频播放量加1
*/
export const addVideoPlayCount = (articleId: number) => {
return service.request({
url: `/api/cultureArticle/addVideoPlayCount?articleId=${articleId}`,
method: 'POST',
})
}
/**
* 展开评论 获取子评论 分页
*/
export const getCommentChildren = (data: CommentChildrenSearchParams) => {
return service.request<BackendServicePageResult<CommentItemDto>>({
url: `/api/cultureComment/comment/children`,
method: 'POST',
data,
})
}
/**
* 个人中心——问吧回答问题列表
*/
export const answerQuestionPage = (data: PageSearchParams) => {
return service.request<BackendServicePageResult<ArticleItemDto>>({
url: `/api/personalCenter/addQuestionPage`,
method: 'POST',
data,
})
}
/**
* 问吧列表 添加问题到回答问题列表
*/
export const addOrCancelToAnswerList = (data: { articleId: number }) => {
return service.request<boolean>({
url: `/api/cultureQuestionAdd/addOrCancel`,
method: 'POST',
data,
})
}
/**
* 问吧 用户添加问题的数量
*/
export const getUserQestionNum = () => {
return service.request<number>({
url: `/api/cultureQuestionAdd/addQuestionNum`,
method: 'POST',
})
}
/**
* 举报帖子
*/
export const addComplaint = (data: { articleId: number; reason: string }) => {
return service.request<boolean>({
url: `/cultureReport/addReport`,
method: 'POST',
data,
})
}
/**
* 问吧-获取回答列表的评论(二级评论)
*/
export const getSecondCommentList = (data: CommentSearchParams) => {
return service.request<BackendServicePageResult<SecondCommentItemDto>>({
url: `/api/cultureComment/getQuestionComment`,
method: 'POST',
data,
})
}
/**
* 问吧 获取二级评论的子评论
*/
export const getSecondCommentChildren = (data: {
pid: number
current: number
size: number
articleId: number
}) => {
return service.request<BackendServicePageResult<CommentItemDto>>({
url: `/api/cultureComment/getAllComment`,
method: 'POST',
data,
})
}
/**
* 根据commentid 获取这个评论的详情
*/
export const getCommentDetail = (id: number) => {
return service.request<CommentItemDto>({
url: `/api/cultureComment/questionCommentData`,
method: 'POST',
data: {
id,
},
})
}
...@@ -8,6 +8,7 @@ export interface ArticleSearchParams extends PageSearchParams { ...@@ -8,6 +8,7 @@ export interface ArticleSearchParams extends PageSearchParams {
type?: ArticleTypeEnum type?: ArticleTypeEnum
sortLogic?: number sortLogic?: number
title?: string title?: string
questionFocus?: number
} }
/** /**
...@@ -61,7 +62,7 @@ export interface AddOrUpdateColumnForm extends AddOrUpdateColumnBase { ...@@ -61,7 +62,7 @@ export interface AddOrUpdateColumnForm extends AddOrUpdateColumnBase {
/** /**
* 添加专栏的DTO * 添加专栏的DTO
*/ */
export interface AddOrUpdateColumnDto extends AddOrUpdateColumnBase { export interface AddOrUpdateColumnDto extends AddOrUpdateColumnBase, PushSettingBase {
tagList: { tagId: number; sort: number }[] tagList: { tagId: number; sort: number }[]
} }
...@@ -85,10 +86,16 @@ export interface AddOrUpdateInterviewForm extends AddOrUpdateInterviewBase { ...@@ -85,10 +86,16 @@ export interface AddOrUpdateInterviewForm extends AddOrUpdateInterviewBase {
tagList: number[] tagList: number[]
} }
interface PushSettingBase {
pushType: SendTypeEnum
pushTime: string
pushList: { valueId: string; valueName: string | number }[]
}
// 推送设置相关的字段
/** /**
* 添加专访的DTO * 添加专访的DTO
*/ */
export interface AddOrUpdateInterviewDto extends AddOrUpdateInterviewBase { export interface AddOrUpdateInterviewDto extends AddOrUpdateInterviewBase, PushSettingBase {
tagList: { tagId: number; sort: number }[] tagList: { tagId: number; sort: number }[]
} }
...@@ -107,6 +114,7 @@ export interface AddOrUpdateVideoDto { ...@@ -107,6 +114,7 @@ export interface AddOrUpdateVideoDto {
isRecommend?: BooleanFlag isRecommend?: BooleanFlag
mainTagId: string | number mainTagId: string | number
tagList: { tagId: number; sort: number }[] | number[] tagList: { tagId: number; sort: number }[] | number[]
relateColumnId?: number
} }
/** /**
...@@ -124,9 +132,10 @@ export interface ArticleItemDto { ...@@ -124,9 +132,10 @@ export interface ArticleItemDto {
faceUrl: string faceUrl: string
videoUrl: string videoUrl: string
description: string description: string
createUserId: number createUserId: string
createTime: number createTime: number
viewCount: number viewCount: number
playCount: number
isRecommend: BooleanFlag isRecommend: BooleanFlag
type: ArticleTypeEnum type: ArticleTypeEnum
isRelateColleague: BooleanFlag isRelateColleague: BooleanFlag
...@@ -144,6 +153,46 @@ export interface ArticleItemDto { ...@@ -144,6 +153,46 @@ export interface ArticleItemDto {
showName: string showName: string
videoDuration: string videoDuration: string
showComment?: boolean showComment?: boolean
relateColumn?: string
rewardNum: number
isExpand: boolean
hasAddQuestion: boolean
cultureCommentListVo: {
articleId: number
avatar: string
children: string
childrenNum: number
content: string
createTime: number
createUserId: number
description: string
faceUrl: string
hasPraise: BooleanFlag
hiddenAvatar: string
hiddenName: string
id: number
isFeatured: BooleanFlag
isRecommend: BooleanFlag
isTop: BooleanFlag
pid: number
postPriseCount: number
praiseCount: number
region: string
regionHide: BooleanFlag
replyId: number
replyName: string
replyUser: string
title: string
type: ArticleTypeEnum
userId: string
viewCount: number
childNum: number
}
relateColumnId: number
sendType: SendTypeEnum
sendTime: string
tagIdList: number[]
pushList: { valueId: string; valueType: number; valueName: string }[]
} }
/** /**
...@@ -168,6 +217,7 @@ export interface ColumnItemDto { ...@@ -168,6 +217,7 @@ export interface ColumnItemDto {
title: string title: string
color: string color: string
sort: number sort: number
id: number
yaColumnVoList: { yaColumnVoList: {
articleId: number articleId: number
collectCount: number collectCount: number
...@@ -182,6 +232,10 @@ export interface ColumnItemDto { ...@@ -182,6 +232,10 @@ export interface ColumnItemDto {
title: string title: string
type: ArticleTypeEnum.COLUMN type: ArticleTypeEnum.COLUMN
viewCount: number viewCount: number
videoDuration: string
showName: string
showAvatar: string
playCount: number
}[] }[]
} }
...@@ -199,6 +253,21 @@ export interface InterviewOptionDto { ...@@ -199,6 +253,21 @@ export interface InterviewOptionDto {
title: string title: string
type: 'column' type: 'column'
} }
/**
* 视频选项
*/
export interface VideoOptionDto {
color: string
createTime: number
createUserId: number
id: number
isDelete: BooleanFlag
sort: number
status: BooleanFlag
title: string
type: 'video'
}
/** /**
* 专访列表Item * 专访列表Item
...@@ -223,6 +292,10 @@ export interface InterviewItemDto { ...@@ -223,6 +292,10 @@ export interface InterviewItemDto {
viewCount: number viewCount: number
}[] }[]
} }
/**
* 视频选项
*/
/** /**
* 评论列表 * 评论列表
*/ */
...@@ -230,6 +303,13 @@ export interface CommentSearchParams extends PageSearchParams { ...@@ -230,6 +303,13 @@ export interface CommentSearchParams extends PageSearchParams {
articleId: number | string articleId: number | string
sortType: number sortType: number
} }
/**
* 获取子评论列表
*/
export interface CommentChildrenSearchParams extends PageSearchParams {
pid: number | string
articleId: number | string
}
/** /**
* 新增评论 * 新增评论
...@@ -237,7 +317,7 @@ export interface CommentSearchParams extends PageSearchParams { ...@@ -237,7 +317,7 @@ export interface CommentSearchParams extends PageSearchParams {
export interface AddCommentDto { export interface AddCommentDto {
articleId: number | string articleId: number | string
content: string content: string
pId?: number | string pid?: number | string
} }
/** /**
...@@ -261,5 +341,85 @@ export interface CommentItemDto { ...@@ -261,5 +341,85 @@ export interface CommentItemDto {
regionHide: number regionHide: number
replyUser: string replyUser: string
replyName: string replyName: string
userId: number userId: string
isHaveChildren: BooleanFlag
childrenNum: number
showChildrenPage: boolean
childrenPageCurrent: number
childrenPageList: CommentItemDto[]
loadingChildren: boolean
showComment: boolean
isExpand: boolean
childNum: number
} }
/**
* 搜索更多专栏查询参数
*/
export interface SearchMoreColumnParams extends PageSearchParams {
columnId: number
sortLogic: number
title?: string
}
/**
* 搜索更多专栏返回参数
*/
export interface SearchMoreColumnItemDto {
articleId: number
collectCount: number
content: string
createTime: number
createUserId: number
description: string
faceUrl: string
hasPraised: boolean
isRecommend: number
playCount: number
praiseCount: number
replyCount: number
showAvatar: string
showName: string
title: string
type: ArticleTypeEnum.COLUMN
videoDuration: string
viewCount: number
}
/**
* 搜索更多视频查询参数
*/
export interface SearchMoreVideoParams extends PageSearchParams {
columnId: number | string
sortLogic: number
title?: string
}
/**
* 搜索更多视频返回参数
*/
export interface SearchMoreVideoItemDto {
articleId: number
collectCount: number
content: string
createTime: number
createUserId: number
description: string
faceUrl: string
hasPraised: boolean
isRecommend: number
playCount: number
praiseCount: number
replyCount: number
showAvatar: string
showName: string
title: string
type: ArticleTypeEnum.VIDEO
videoDuration: string
viewCount: number
}
/**
* 获取问吧二级评论的返回参数
*/
export type SecondCommentItemDto = CommentItemDto
import service from '@/utils/request/index' import service from '@/utils/request/index'
import type { AddOrUpdateTagDto, BackendTagListItemDto, BackendTagSearchParams } from './types' import type {
ArticleSearchParams,
AddOrUpdateCarouselDto,
BackendArticleListItemDto,
BackendCarouselListItemDto,
} from './types'
import type { BackendServicePageResult } from '@/utils/request/types' import type { BackendServicePageResult } from '@/utils/request/types'
// 后台管理标签相关接口 // 后台管理标签相关接口
...@@ -7,7 +12,7 @@ import type { BackendServicePageResult } from '@/utils/request/types' ...@@ -7,7 +12,7 @@ import type { BackendServicePageResult } from '@/utils/request/types'
* 获取轮播图列表 不分页 数量不多 * 获取轮播图列表 不分页 数量不多
*/ */
export const getCarouselList = () => { export const getCarouselList = () => {
return service.request<BackendServicePageResult<BackendTagListItemDto>>({ return service.request<BackendCarouselListItemDto[]>({
url: '/api/cultureCarousel/listNoPage', url: '/api/cultureCarousel/listNoPage',
method: 'POST', method: 'POST',
data: {}, data: {},
...@@ -15,7 +20,7 @@ export const getCarouselList = () => { ...@@ -15,7 +20,7 @@ export const getCarouselList = () => {
} }
/** /**
* 添加轮播 * 添加轮播
*/ */
export const addCarousel = (data: AddOrUpdateCarouselDto) => { export const addCarousel = (data: AddOrUpdateCarouselDto) => {
return service.request({ return service.request({
...@@ -37,11 +42,12 @@ export const editCarousel = (data: AddOrUpdateCarouselDto) => { ...@@ -37,11 +42,12 @@ export const editCarousel = (data: AddOrUpdateCarouselDto) => {
} }
/** /**
* 更改发布状态 * 根据类型获取文章列表
*/ */
export const deleteCarousel = (id: number) => { export const getArticleList = (data: ArticleSearchParams) => {
return service.request<boolean>({ return service.request<BackendServicePageResult<BackendArticleListItemDto>>({
url: `/api/cultureCarousel/updateRelease?id=${id}`, url: `/api/cultureArticle/listByPage`,
method: 'POST', method: 'POST',
data,
}) })
} }
import { ArticleTypeEnum } from '@/constants'
import type { PageSearchParams } from '@/utils/request/types'
export interface ArticleSearchParams extends PageSearchParams {
title?: string
type?: ArticleTypeEnum
}
export interface AddOrUpdateCarouselDto {
articleTitle: string
assetUrl: string
id?: number
isRelease: number
sort: number
type: number
url: string
}
export interface BackendCarouselListItemDto {
articleId: number
articleTitle: string
assetUrl: string
id: number
isRelease: number
lastName: string
lastTime: number
sort: number
type: number
url: string
}
export interface BackendArticleListItemDto {
collectionCount: number
content: string
createTime: number
createUserId: number
description: string
faceUrl: string
hasAddQuestion: boolean
hasCollect: boolean
hasPraised: boolean
id: number
isRecommend: number
isRelateColleague: number
praiseCount: number
releaseStatus: number
replyCount: number
showAvatar: string
showName: string
tagNameList: string[]
title: string
type: ArticleTypeEnum
videoDuration: string
videoUrl: string
viewCount: number
}
import service from '@/utils/request/index' import service from '@/utils/request/index'
import type { AddOrUpdateTagDto, BackendTagListItemDto, BackendTagSearchParams } from './types' import type {
ChangeUsageStatusDto,
AuditCaseDto,
BackendCaseListItemDto,
CaseListSearchParams,
} from './types'
import type { BackendServicePageResult } from '@/utils/request/types' import type { BackendServicePageResult } from '@/utils/request/types'
// 后台管理案例库相关接口 // 后台管理案例库相关接口
/** /**
* 案例库后台列表 * 案例库后台列表
*/ */
export const getCaseList = (data: PageSearchParams) => { export const getCaseList = (data: CaseListSearchParams) => {
return service.request<BackendServicePageResult<BackendTagListItemDto>>({ return service.request<BackendServicePageResult<BackendCaseListItemDto>>({
url: '/api/cultureCase/caseListByPage', url: '/api/cultureCase/caseListByPage',
method: 'POST', method: 'POST',
data, data,
...@@ -15,9 +20,19 @@ export const getCaseList = (data: PageSearchParams) => { ...@@ -15,9 +20,19 @@ export const getCaseList = (data: PageSearchParams) => {
} }
/** /**
* 删除案例库
*/
export const deleteCase = (id: number) => {
return service.request({
url: `/api/cultureCase/deleteCase?id=${id}`,
method: 'POST',
})
}
/**
* 审核接口 * 审核接口
*/ */
export const auditCase = (data: AddOrUpdateCarouselDto) => { export const auditCase = (data: AuditCaseDto) => {
return service.request({ return service.request({
url: '/api/cultureCase/auditCase', url: '/api/cultureCase/auditCase',
method: 'POST', method: 'POST',
...@@ -26,11 +41,13 @@ export const auditCase = (data: AddOrUpdateCarouselDto) => { ...@@ -26,11 +41,13 @@ export const auditCase = (data: AddOrUpdateCarouselDto) => {
} }
/** /**
* 删除案例库 * 修改使用状态
*/ */
export const deleteCase = (id: number) => {
export const changeUsageStatus = (data: ChangeUsageStatusDto) => {
return service.request({ return service.request({
url: `/api/cultureCase/deleteCase?id=${id}`, url: '/api/cultureCase/userCase',
method: 'POST', method: 'POST',
data,
}) })
} }
import { UsageStatusEnum, AuditStatusEnum } from '@/constants'
import type { PageSearchParams } from '@/utils/request/types'
export interface CaseListSearchParams extends PageSearchParams {
isAudit?: AuditStatusEnum
isUse?: UsageStatusEnum
title?: string
}
export interface BackendCaseListItemDto {
caseType: string
content: string
createTime: number
createUser: number
cultureKeywordMain: string
cultureKeywordSecond: string[]
cultureRelation: number
dataSources: number
depIdList: string[]
depNameList: string[]
deptId: string
deptName: string
id: number
integrity: number
isAudit: number
isDelete: number
isDispose: number
isUse: number
mainScene: string
minorScene: string
number: string
sceneKeywordMain: string
sceneKeywordSecond: string[]
sourceProject: string
sourceTime: number
sourceType: string
sourceUser: number
title: string
yearKeywordMain: string
yearKeywordSecond: string[]
sourceUserName: string
sourceUserWorkNo: string
}
export interface ChangeUsageStatusDto {
id: number
isUse: UsageStatusEnum
}
export interface AuditCaseDto {
id: number
isAudit: AuditStatusEnum
}
...@@ -16,6 +16,7 @@ export interface BackendColumnListItemDto { ...@@ -16,6 +16,7 @@ export interface BackendColumnListItemDto {
color: string color: string
createTime: number createTime: number
createUserId: number createUserId: number
content: string
id: number id: number
isDelete: number isDelete: number
postCount: number postCount: number
......
import service from '@/utils/request/index' import service from '@/utils/request/index'
import type { AddOrUpdateTagDto, BackendTagListItemDto, BackendTagSearchParams } from './types' import type {
BackendShopItemDto,
AddOrUpdateShopItemDto,
BackendExchangeListSearchParams,
BackendShopListSearchParams,
} from './types'
import type { BackendServicePageResult } from '@/utils/request/types' import type { BackendServicePageResult } from '@/utils/request/types'
// 后台管理 积分商城相关接口 // 后台管理 积分商城相关接口
/** /**
* 商品配置列表 * 商品配置列表
*/ */
export const getShopItemList = (params: BackendTagSearchParams) => { export const getShopItemList = (params: BackendShopListSearchParams) => {
return service.request<BackendServicePageResult<BackendTagListItemDto>>({ return service.request<BackendServicePageResult<BackendShopItemDto>>({
url: '/api/culture/shop/item/productList', url: '/api/culture/shop/item/productList',
method: 'POST', method: 'POST',
data: params, data: params,
...@@ -17,8 +22,8 @@ export const getShopItemList = (params: BackendTagSearchParams) => { ...@@ -17,8 +22,8 @@ export const getShopItemList = (params: BackendTagSearchParams) => {
/** /**
* 新增/编辑商品 * 新增/编辑商品
*/ */
export const addOrUpdateShopItem = (data: AddOrUpdateTagDto) => { export const addOrUpdateShopItem = (data: AddOrUpdateShopItemDto) => {
return service.request<BackendServicePageResult<BackendTagListItemDto>>({ return service.request({
url: '/api/culture/shop/item/addOrUpdate', url: '/api/culture/shop/item/addOrUpdate',
method: 'POST', method: 'POST',
data, data,
...@@ -37,8 +42,8 @@ export const deleteShopItem = (id: number) => { ...@@ -37,8 +42,8 @@ export const deleteShopItem = (id: number) => {
/** /**
* 后台商品领用列表 * 后台商品领用列表
*/ */
export const getBackendExchangeList = (data: BackendTagSearchParams) => { export const getBackendExchangeList = (data: BackendExchangeListSearchParams) => {
return service.request<BackendServicePageResult<BackendTagListItemDto>>({ return service.request<BackendServicePageResult<BackendShopItemDto>>({
url: '/api/culture/shop/order/background/productList', url: '/api/culture/shop/order/background/productList',
method: 'POST', method: 'POST',
data, data,
...@@ -48,7 +53,7 @@ export const getBackendExchangeList = (data: BackendTagSearchParams) => { ...@@ -48,7 +53,7 @@ export const getBackendExchangeList = (data: BackendTagSearchParams) => {
/** /**
* 发放 取消发放 * 发放 取消发放
*/ */
export const issueProduct = (data) => { export const issueProduct = (data: { id: number; status: number; memo?: string }) => {
return service.request({ return service.request({
url: `/api/culture/shop/order/issueProduct`, url: `/api/culture/shop/order/issueProduct`,
method: 'POST', method: 'POST',
......
import type { PageSearchParams } from '@/utils/request/types' import type { PageSearchParams } from '@/utils/request/types'
export interface BackendTagSearchParams extends PageSearchParams { import { BooleanFlag, ShopGoodsTypeEnum } from '@/constants'
title: string export interface BackendShopListSearchParams extends PageSearchParams {
type: string name?: string
itemType?: ShopGoodsTypeEnum
region?: string
enable?: 0 | 1
} }
export interface BackendShopItemDto {
export interface AddOrUpdateTagDto {
id?: number id?: number
color: string sort: number
description: string enable: BooleanFlag
imageUrl: string
itemType: ShopGoodsTypeEnum
name: string
price: number
region: string
title: string title: string
type: string description: string
imgUrl: string
stock: number
status: number
} }
export interface BackendTagListItemDto { export interface AddOrUpdateShopItemDto {
color: string id?: number
createId: number title: string
createTime: number
description: string description: string
id: number
imgUrl: string imgUrl: string
style: number price: number
title: string stock: number
type: string status: number
}
export interface BackendExchangeListSearchParams extends PageSearchParams {
source: string
status?: 0 | 1 | 2
itemName?: string
} }
import service from '@/utils/request/index' import service from '@/utils/request/index'
import type { AddOrUpdateCaseDto } from './types' import type { AddOrUpdateCaseDto, CaseDetailDto } from './types'
// 案例库相关的接口 // 案例库相关的接口
...@@ -24,3 +24,23 @@ export const addOrUpdateCase = (data: AddOrUpdateCaseDto) => { ...@@ -24,3 +24,23 @@ export const addOrUpdateCase = (data: AddOrUpdateCaseDto) => {
data, data,
}) })
} }
/**
* 获取案例详情
*/
export const getCaseDetail = (id: number) => {
return service.request<CaseDetailDto>({
url: `/api/cultureCase/getCaseDetail?caseId=${id}`,
method: 'POST',
})
}
/**
* 删除案例库
*/
export const deleteCase = (id: number) => {
return service.request({
url: `/api/cultureCase/deleteCase?id=${id}`,
method: 'POST',
})
}
...@@ -3,17 +3,32 @@ ...@@ -3,17 +3,32 @@
*/ */
import { TagTypeEnum, TagLevelEnum, BooleanFlag, ReleaseStatusTypeEnum } from '@/constants' import { TagTypeEnum, TagLevelEnum, BooleanFlag, ReleaseStatusTypeEnum } from '@/constants'
export type TagItemDto = { type TagItemDto = {
tagId: number tagId: number
type: TagTypeEnum type: TagTypeEnum
keywordType: TagLevelEnum keywordType: TagLevelEnum
} }
export interface AddOrUpdateCaseDto { export interface AddOrUpdateCaseDto {
id?: number
content: string content: string
title: string deptId: string
tagRelationDtoList: TagItemDto[] deptName: string
isSync: BooleanFlag isSync: BooleanFlag
releaseStatus: ReleaseStatusTypeEnum releaseStatus: ReleaseStatusTypeEnum
sourceUser: string
tagRelationDtoList: TagItemDto[]
title: string
}
export interface CaseDetailDto {
id: number
title: string
content: string
caseTags: TagItemDto[]
depIdList: number[]
depNameList: string[]
isSync: BooleanFlag
cultureKeywordMain: string
cultureKeywordSub: string[]
sceneKeywordMain: string
sceneKeywordSub: string[]
} }
// 常规的接口 // 常规的接口
import service from '@/utils/request/index' // import service from '@/utils/request/index'
import type { FielItemDto } from './types' // import type { FielItemDto } from './types'
/** /**
* 获取常规的接口 * 获取常规的接口
*/ */
...@@ -21,14 +21,55 @@ import type { FielItemDto } from './types' ...@@ -21,14 +21,55 @@ import type { FielItemDto } from './types'
/** /**
* 暂时调用oa正式接口 * 暂时调用oa正式接口
*/ */
import axios from 'axios' import axios, { type AxiosRequestConfig } from 'axios'
export const uploadFile = (file: File, onProgress?: (progress: number) => void) => { type UploadFileResponseItem = {
createTime: string
createUser: number
fileBucket: string
fileId: string
fileName: string
filePath: string
fileSizeKb: number
fileSuffix: string
finalName: string
realPath: string
updateTime: string
updateUser: string
}
// 单个文件上传
export const uploadFile = (
file: File,
options: {
onProgress?: (progress: number) => void
} = {},
): {
cancel: () => void
promise: Promise<UploadFileResponseItem>
} => {
const formData = new FormData() const formData = new FormData()
formData.append('fileList', file) formData.append('fileList', file)
return axios.post('http://47.112.96.71:8082/mobiles/uploadFile', formData, {
onUploadProgress: (progressEvent) => { const { onProgress } = options
const controller = new AbortController()
const axiosOptions: AxiosRequestConfig = {
signal: controller.signal,
}
if (onProgress) {
axiosOptions.onUploadProgress = (progressEvent) => {
const percentCompleted = Math.round((progressEvent.loaded * 100) / (progressEvent.total || 1)) const percentCompleted = Math.round((progressEvent.loaded * 100) / (progressEvent.total || 1))
onProgress?.(percentCompleted) onProgress?.(percentCompleted)
}
}
return {
cancel: () => {
controller.abort()
}, },
}) promise: axios
.post('http://47.112.96.71:8082/mobiles/uploadFile', formData, axiosOptions)
.then((data) => data.data.data[0] as UploadFileResponseItem),
}
} }
...@@ -3,8 +3,6 @@ export * from './task' ...@@ -3,8 +3,6 @@ export * from './task'
export * from './sign' export * from './sign'
export * from './article' export * from './article'
export * from './shop' export * from './shop'
// export * from './column'
// export * from './interview'
export * from './tag' export * from './tag'
export * from './article' export * from './article'
export * from './user' export * from './user'
...@@ -14,12 +12,12 @@ export * from './practice' ...@@ -14,12 +12,12 @@ export * from './practice'
export * from './common' export * from './common'
export * from './login' export * from './login'
export * from './article' export * from './article'
export * from './online'
export * from './otherUserPage'
// 导出类型 // 导出类型
export * from './task/types' export * from './task/types'
export * from './shop/types' export * from './shop/types'
export * from './article/types' export * from './article/types'
// export * from './column/types'
// export * from './interview/types'
export * from './tag/types' export * from './tag/types'
export * from './article/types' export * from './article/types'
export * from './user/types' export * from './user/types'
...@@ -29,3 +27,5 @@ export * from './practice/types' ...@@ -29,3 +27,5 @@ export * from './practice/types'
export * from './common/types' export * from './common/types'
export * from './login/types' export * from './login/types'
export * from './article/types' export * from './article/types'
export * from './online/types'
export * from './otherUserPage/types'
// 专栏列表 // 专栏列表
import service from '@/utils/request/index' import service from '@/utils/request/index'
import type { ColumnOptionDto } from './types' import type { InterviewOptionDto } from './types'
/** /**
* 获取专栏列表 * 获取专栏列表
*/ */
export const getInterviewOptions = () => { export const getInterviewOptions = () => {
return service.request<ColumnOptionDto[]>({ return service.request<InterviewOptionDto[]>({
url: '/api/cultureColumn/listNoPage?type=interview', url: '/api/cultureColumn/listNoPage?type=interview',
method: 'POST', method: 'POST',
}) })
......
import service from '@/utils/request/index' import service from '@/utils/request/index'
import type { LoginParams, LoginResponseDto } from './types' import type { LoginParams, LoginResponseDto, GetWxSignatureResponseDto } from './types'
import { app_config } from '@/config'
/** /**
* 登录 —— 根据 邮箱密码登录 * 登录 —— 根据 邮箱密码登录
...@@ -41,10 +42,10 @@ export const loginByCode = ({ ...@@ -41,10 +42,10 @@ export const loginByCode = ({
* 生成随机密钥-切换官方账号用 * 生成随机密钥-切换官方账号用
*/ */
interface GenerateLoginKeyData { interface GenerateLoginKeyData {
cutEmail: string cutEmail?: string
timestamp: number timestamp: number
type: 1 | 2 // 1: 多平台跳转 2: 官方账号切换 type: 1 | 2 // 1: 多平台跳转 清除缓存 2: 官方账号切换
userId: number userId: string
} }
export const generateLoginKey = (data: GenerateLoginKeyData) => { export const generateLoginKey = (data: GenerateLoginKeyData) => {
return service.request<string>({ return service.request<string>({
...@@ -53,3 +54,18 @@ export const generateLoginKey = (data: GenerateLoginKeyData) => { ...@@ -53,3 +54,18 @@ export const generateLoginKey = (data: GenerateLoginKeyData) => {
data, data,
}) })
} }
/**
* 企业微信获取密钥
*/
export const getWxSignature = (url: string) => {
const loginType = app_config[import.meta.env.MODE]?.loginType
return service.request<GetWxSignatureResponseDto>({
url: '/api/auth/getSign',
method: 'POST',
data: {
url,
reqType: loginType,
},
})
}
...@@ -16,6 +16,7 @@ export interface LoginResponseDto { ...@@ -16,6 +16,7 @@ export interface LoginResponseDto {
permissions: any[] | null permissions: any[] | null
userRelated: any[] | null userRelated: any[] | null
isOfficialAccount: boolean | null isOfficialAccount: boolean | null
isAdmin: boolean
firstDeptId: number | null firstDeptId: number | null
password: string | null password: string | null
enabled: boolean enabled: boolean
...@@ -25,4 +26,16 @@ export interface LoginResponseDto { ...@@ -25,4 +26,16 @@ export interface LoginResponseDto {
credentialsNonExpired: boolean credentialsNonExpired: boolean
accountNonLocked: boolean accountNonLocked: boolean
token: string token: string
userId: string
hiddenAvatar: string
hiddenName: string
signature: string
}
export interface GetWxSignatureResponseDto {
timestamp: string
nonceStr: string
signature: string
corpid: string
agentid: string
} }
import service from '@/utils/request/index'
// 在线时长的功能
/**
* 获取当前用户在线时长
*/
export const getTodayOnlineSeconds = () => {
return service.request<number>({
url: '/api/personalCenter/getTodayOnlineSeconds',
method: 'POST',
// data: {},
})
}
/**
* 暂时这种轮询方案 30s调一次
*/
export const heartbeat = () => {
return service.request<boolean>({
url: '/api/personalCenter/heartbeat',
method: 'POST',
data: {},
})
}
export type a = string
import service from '@/utils/request/index'
import type { BooleanFlag } from '@/constants'
import type { OtherUserInfoDto, OtherUserPostDataSearchParams, OtherUserPostDataDto } from './types'
import type { BackendServicePageResult } from '@/utils/request/types'
// 其他用户页面数据
/**
* 获取其他用户的用户信息
*/
export const getOtherUserData = (data: { userId: string; isReal: BooleanFlag }) => {
return service.request<OtherUserInfoDto>({
url: '/api/personalCenter/userData',
method: 'POST',
data,
})
}
/**
* 显示其他用户的发帖数据
*/
export const getOtherUserPostData = (data: OtherUserPostDataSearchParams) => {
return service.request<BackendServicePageResult<OtherUserPostDataDto>>({
url: '/api/personalCenter/getUserArticleData',
method: 'POST',
data,
})
}
/**
* 添加或更新案例库DTO
*/
import { ArticleTypeEnum, BooleanFlag, ReleaseStatusTypeEnum } from '@/constants'
import type { PageSearchParams } from '@/utils/request/types'
export interface OtherUserInfoDto {
ayabiAvailable: number
ayabiTotal: number
createTime: string
expTotal: number
id: number
isDelete: BooleanFlag
level: number
remark: string
showAvatar: string
showName: string
signature: string
updateTime: string
}
export interface OtherUserPostDataSearchParams extends PageSearchParams {
createUserId: string
isReal: BooleanFlag
}
export interface OtherUserPostDataDto {
collectionCount: number
content: string
createTime: number
createUserId: string
cultureCommentListVo: null
description: string
faceUrl: string
hasAddQuestion: BooleanFlag
hasCollect: BooleanFlag
hasPraised: BooleanFlag
id: number
isRecommend: BooleanFlag
isRelateColleague: BooleanFlag
playCount: number
praiseCount: number
releaseStatus: ReleaseStatusTypeEnum
replyCount: number
showAvatar: string
showName: string
tagNameList: string[]
title: string
type: ArticleTypeEnum
videoDuration: string
videoUrl: string
viewCount: number
}
...@@ -22,75 +22,10 @@ export interface AddOrUpdatePracticeDto { ...@@ -22,75 +22,10 @@ export interface AddOrUpdatePracticeDto {
* 搜索文章的参数 * 搜索文章的参数
*/ */
export interface PracticeSearchParams extends PageSearchParams { export interface PracticeSearchParams extends PageSearchParams {
sortLogic?: number sortLogic: number
tagIdList?: number[] tagIdList: number[]
} deptIdList: string[]
/**
* 添加或更新文章DTO(带枚举版本)
*/
export interface AddOrUpdateArticleDto {
/** 内容 */
content?: string
/** 创建人id */
createUserId?: number
/** 描述 */
description?: string
/** 封面图 */
faceUrl?: string
/** id,编辑时必传 */
id?: number
/** 是否关联同事 */
isRelateColleague?: number
/** 主标签id */
mainTagId?: number
/** 发布状态 */
releaseStatus?: ReleaseStatusTypeEnum
/** 标签列表 */
tagList?: { tagId: number; sort: number }[]
/** 标题 */
title?: string title?: string
/** 文章类型 */
type?: ArticleTypeEnum
/** 视频url */
videoUrl?: string
}
/**
* 文章详情
*/
// 更严格的类型定义
export interface ArticleItemDto {
id: number
title: string
content: string
faceUrl: string
videoUrl: string
description: string
createUserId: number
createTime: number
viewCount: number
isRecommend: BooleanFlag
type: ArticleTypeEnum
isRelateColleague: BooleanFlag
releaseStatus: ReleaseStatusTypeEnum
tagNameList: string[]
praiseCount: number
collectionCount: number
replyCount: number
hasPraised: BooleanFlag
} }
/** /**
......
import service from '@/utils/request/index' import service from '@/utils/request/index'
import type { BackendServicePageResult, PageSearchParams } from '@/utils/request/types' import type { BackendServicePageResult } from '@/utils/request/types'
import type { import type {
ExchangeGoodsParams, ExchangeGoodsParams,
ExchangeGoodsRecordItemDto, ExchangeGoodsRecordItemDto,
ShopItemDto,
ShopSearchParams, ShopSearchParams,
YaBiData, YaBiData,
ExchangeYabiRecordItemDto, ExchangeYabiRecordItemDto,
ExchangeGoodsRecordSearchParams, ExchangeGoodsRecordSearchParams,
BackendShopItemDto, BackendShopItemDto,
ExchangeYabiRecordSearchParams,
} from './types' } from './types'
/** /**
* 获取用户亚币相关数据 * 获取用户亚币相关数据
...@@ -57,7 +57,7 @@ export const getExchangeGoodsRecordList = (data: ExchangeGoodsRecordSearchParams ...@@ -57,7 +57,7 @@ export const getExchangeGoodsRecordList = (data: ExchangeGoodsRecordSearchParams
/** /**
* 获取用户YA币兑换记录 * 获取用户YA币兑换记录
*/ */
export const getExchangeYabiRecordList = (data: PageSearchParams) => { export const getExchangeYabiRecordList = (data: ExchangeYabiRecordSearchParams) => {
return service.request<BackendServicePageResult<ExchangeYabiRecordItemDto>>({ return service.request<BackendServicePageResult<ExchangeYabiRecordItemDto>>({
url: '/api/culture/action/record/yabiList', url: '/api/culture/action/record/yabiList',
method: 'POST', method: 'POST',
......
...@@ -91,3 +91,11 @@ export interface ExchangeYabiRecordItemDto { ...@@ -91,3 +91,11 @@ export interface ExchangeYabiRecordItemDto {
subType: string subType: string
userId: number userId: number
} }
/**
* 获取用户YA币兑换记录搜索参数
*/
export interface ExchangeYabiRecordSearchParams extends PageSearchParams {
type: ShopGoodsTypeEnum
dateRange: [number, number]
}
...@@ -4,12 +4,12 @@ import type { TagItemDto } from './types' ...@@ -4,12 +4,12 @@ import type { TagItemDto } from './types'
/** /**
* 获取标签 不分页 * 获取标签 不分页
*/ */
export const getTagList = () => { export const getTagList = (type?: string) => {
return service.request<TagItemDto[]>({ return service.request<TagItemDto[]>({
url: '/api/cultureTag/listNoPage', url: '/api/cultureTag/listNoPage',
method: 'POST', method: 'POST',
data: { data: {
type: 'culture', type,
}, },
}) })
} }
...@@ -10,8 +10,19 @@ import type { ...@@ -10,8 +10,19 @@ import type {
SelfCollectDetailDto, SelfCollectDetailDto,
SelfPraiseSearchParams, SelfPraiseSearchParams,
SelfPraiseDetailDto, SelfPraiseDetailDto,
OfficialAccountItemDto,
SelfCaseItemDto,
SelfCommentItemDto,
SelfCommentSearchParams,
SelfDraftSearchParams,
SelfTaskSearchParams,
SelfTaskItemDto,
ComplaintListItemDto,
AuditComplaintDto,
ComplaintListSearchParams,
SelfCaseSearchParams,
} from './types' } from './types'
import type { BackendServicePageResult, PageSearchParams } from '@/utils/request/types' import type { BackendServicePageResult } from '@/utils/request/types'
/** /**
* 更新用户信息 * 更新用户信息
...@@ -28,7 +39,7 @@ export const updateUserInfo = (data: UpdateUserInfoDto) => { ...@@ -28,7 +39,7 @@ export const updateUserInfo = (data: UpdateUserInfoDto) => {
* 是否有官方账号权限 * 是否有官方账号权限
*/ */
export const hasOfficialAccount = () => { export const hasOfficialAccount = () => {
return service.request<[]>({ return service.request<OfficialAccountItemDto[]>({
url: '/api/personalCenter/getIsOfficial', url: '/api/personalCenter/getIsOfficial',
method: 'POST', method: 'POST',
data: {}, data: {},
...@@ -49,7 +60,7 @@ export const getSelfPublishList = (data: SelfPublishSearchParams) => { ...@@ -49,7 +60,7 @@ export const getSelfPublishList = (data: SelfPublishSearchParams) => {
/** /**
* 获取我的草稿列表 * 获取我的草稿列表
*/ */
export const getSelfDraftList = (data: PageSearchParams) => { export const getSelfDraftList = (data: SelfDraftSearchParams) => {
return service.request<BackendServicePageResult<SelfPublishDetailDto>>({ return service.request<BackendServicePageResult<SelfPublishDetailDto>>({
url: '/api/personalCenter/selfDraft', url: '/api/personalCenter/selfDraft',
method: 'POST', method: 'POST',
...@@ -82,16 +93,8 @@ export const getSelfPraiseList = (data: SelfPraiseSearchParams) => { ...@@ -82,16 +93,8 @@ export const getSelfPraiseList = (data: SelfPraiseSearchParams) => {
/** /**
* 获取我的案例库列表 * 获取我的案例库列表
*/ */
export const getSelfCaseList = (data: PageSearchParams) => { export const getSelfCaseList = (data: SelfCaseSearchParams) => {
return service.request< return service.request<BackendServicePageResult<SelfCaseItemDto>>({
BackendServicePageResult<{
id: number
title: string
content: string
createTime: string
updateTime: string
}>
>({
url: '/api/personalCenter/selfCase', url: '/api/personalCenter/selfCase',
method: 'POST', method: 'POST',
data, data,
...@@ -101,8 +104,8 @@ export const getSelfCaseList = (data: PageSearchParams) => { ...@@ -101,8 +104,8 @@ export const getSelfCaseList = (data: PageSearchParams) => {
/** /**
* 获取我的任务列表 * 获取我的任务列表
*/ */
export const getSelfTaskList = (data: PageSearchParams) => { export const getSelfTaskList = (data: SelfTaskSearchParams) => {
return service.request<BackendServicePageResult<SelfPublishDetailDto>>({ return service.request<BackendServicePageResult<SelfTaskItemDto>>({
url: '/api/personalCenter/selfTaskConfig', url: '/api/personalCenter/selfTaskConfig',
method: 'POST', method: 'POST',
data, data,
...@@ -112,8 +115,8 @@ export const getSelfTaskList = (data: PageSearchParams) => { ...@@ -112,8 +115,8 @@ export const getSelfTaskList = (data: PageSearchParams) => {
/** /**
* 评论回复 * 评论回复
*/ */
export const getSelfCommentList = (data: PageSearchParams) => { export const getSelfCommentList = (data: SelfCommentSearchParams) => {
return service.request({ return service.request<BackendServicePageResult<SelfCommentItemDto>>({
url: '/api/personalCenter/selfComment', url: '/api/personalCenter/selfComment',
method: 'POST', method: 'POST',
data, data,
...@@ -141,3 +144,25 @@ export const auditArticle = (data: AuditArticleDto) => { ...@@ -141,3 +144,25 @@ export const auditArticle = (data: AuditArticleDto) => {
data, data,
}) })
} }
/**
* 举报列表
*/
export const getComplaintList = (data: ComplaintListSearchParams) => {
return service.request<BackendServicePageResult<ComplaintListItemDto>>({
url: '/cultureReport/getReportList',
method: 'POST',
data,
})
}
/**
* 审核举报
*/
export const auditComplaint = (data: AuditComplaintDto) => {
return service.request({
url: '/cultureReport/auditReport',
method: 'POST',
data,
})
}
import { ArticleTypeEnum, AuditStatusEnum } from '@/constants' import {
ArticleTypeEnum,
AuditStatusEnum,
BooleanFlag,
CommentTypeEnum,
ReleaseStatusTypeEnum,
TaskDateLimitTypeEnum,
TaskTypeEnum,
} from '@/constants'
import type { PageSearchParams } from '@/utils/request/types' import type { PageSearchParams } from '@/utils/request/types'
export interface SelfPublishSearchParams extends PageSearchParams { export interface SelfPublishSearchParams extends PageSearchParams {
...@@ -21,7 +29,7 @@ export interface SelfPublishDetailDto { ...@@ -21,7 +29,7 @@ export interface SelfPublishDetailDto {
replyCount: number replyCount: number
tagNameList: string[] tagNameList: string[]
title: string title: string
type: string type: ArticleTypeEnum
videoUrl: string videoUrl: string
viewCount: number viewCount: number
} }
...@@ -45,7 +53,7 @@ export interface SelfCollectDetailDto { ...@@ -45,7 +53,7 @@ export interface SelfCollectDetailDto {
replyCount: number replyCount: number
tagNameList: string[] tagNameList: string[]
title: string title: string
type: string type: ArticleTypeEnum
viewCount: number viewCount: number
} }
...@@ -54,6 +62,11 @@ export interface SelfPraiseSearchParams extends PageSearchParams { ...@@ -54,6 +62,11 @@ export interface SelfPraiseSearchParams extends PageSearchParams {
type: ArticleTypeEnum type: ArticleTypeEnum
} }
// 我的案例库搜索
export interface SelfCaseSearchParams extends PageSearchParams {
releaseStatus: ReleaseStatusTypeEnum
}
export interface SelfPraiseDetailDto { export interface SelfPraiseDetailDto {
collectionCount: number collectionCount: number
content: string content: string
...@@ -70,11 +83,16 @@ export interface SelfPraiseDetailDto { ...@@ -70,11 +83,16 @@ export interface SelfPraiseDetailDto {
replyCount: number replyCount: number
tagNameList: string[] tagNameList: string[]
title: string title: string
type: string type: ArticleTypeEnum
videoUrl: string videoUrl: string
viewCount: number viewCount: number
} }
//草稿搜索
export interface SelfDraftSearchParams extends PageSearchParams {
type: ArticleTypeEnum
}
// 我的草稿 详情 // 我的草稿 详情
export interface SelfDraftDetailDto { export interface SelfDraftDetailDto {
id: number id: number
...@@ -117,7 +135,7 @@ export interface AuditListItemDto { ...@@ -117,7 +135,7 @@ export interface AuditListItemDto {
showName: string showName: string
tagNameList: string[] tagNameList: string[]
title: string title: string
type: string type: ArticleTypeEnum
viewCount: number viewCount: number
} }
...@@ -126,6 +144,182 @@ export interface AuditListItemDto { ...@@ -126,6 +144,182 @@ export interface AuditListItemDto {
*/ */
export interface AuditArticleDto { export interface AuditArticleDto {
articleId: number articleId: number
auditResult: Exclude<AuditStatusEnum, AuditStatusEnum.UNAUDITED> auditResult: AuditStatusEnum
auditRemark?: string auditRemark?: string
} }
/**
* 官方账号Item
*/
export interface OfficialAccountItemDto {
account: string
address: string
avatar: string
birthday: string
createTime: null
createUser: null
deptId: number
directLeader: string
email: string
entryDate: null
hadFansPoint: number
hiddenAvatar: string
hiddenName: string
interactiveMessageCount: number
isOfficialAccount: BooleanFlag
isReceiveMsg: BooleanFlag
jobNumId: string
level: number
loginTime: null
name: null
officialTag: string
password: null
passwordChangeStatus: number
passwordUpdateTime: null
phone: null
region: string
regionHide: BooleanFlag
roleId: string
salt: null
sex: null
signature: null
status: string
updateTime: string
updateUser: number
userId: number
version: null
}
/**
* 我的案例库列表
*/
export interface SelfCaseItemDto {
caseType: string
content: string
createTime: number
createUser: number
cultureKeywordMain: string
cultureKeywordSecond: string
cultureRelation: number
dataSources: number
deptId: string
deptName: string
id: number
integrity: number
isAudit: AuditStatusEnum
isDelete: number
isDispose: number
isUse: number
mainScene: string
minorScene: string
number: string
sceneKeywordMain: string
sceneKeywordSecond: string
sourceProject: string
sourceTime: number
sourceType: string
sourceUser: number
title: string
yearKeywordMain: null
yearKeywordSecond: null
}
/**
* 我的评论列表查询参数
*/
export interface SelfCommentSearchParams extends PageSearchParams {
messageType: CommentTypeEnum
type: ArticleTypeEnum
}
/**
* 我的评论列表
*/
export interface SelfCommentItemDto {
articleId: number
avatar: string
children: null
content: string
createTime: number
createUserId: number
description: string
faceUrl: string
hasPraise: boolean
hiddenAvatar: string
hiddenName: string
id: number
isFeatured: boolean
isRecommend: boolean
isTop: boolean
pid: number
postPriseCount: number
region: string
regionHide: boolean
replyId: number
replyName: string
replyUser: string
title: string
type: ArticleTypeEnum
userId: number
viewCount: number
}
// 我的任务
export interface SelfTaskSearchParams extends PageSearchParams {
taskType: TaskTypeEnum
}
export interface SelfTaskItemDto {
completedTasks: number
currentCount: number
description: string
id: number
jumpUrl: string
limitCount: number
limitType: TaskDateLimitTypeEnum
periodKey: string
rewardType: number
rewardValue: number
status: number
svgName: string
taskKey: string
taskType: number
title: string
totalTasks: number
}
/**
* 举报列表item
*/
export interface ComplaintListItemDto {
articleId: number
auditId: number
auditName: string
auditTime: number
createId: number
createName: string
createTime: number
id: number
isDelete: number
reason: string
showAvatar: string
showName: string
status: number
title: string
type: ArticleTypeEnum
}
/**
* 举报列表搜索参数
*/
export interface ComplaintListSearchParams extends PageSearchParams {
status: AuditStatusEnum
}
/**
* 审核举报
*/
export interface AuditComplaintDto {
id: number
status: AuditStatusEnum
remark?: string
}
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764644261808" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6597" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M248.482281 938.000324c-4.306072 0-8.592702-1.336438-12.211113-3.967358-6.395664-4.646833-9.600659-12.521175-8.264221-20.314675l48.430012-282.363949L71.288626 431.382914c-5.66093-5.519714-7.698333-13.772678-5.255701-21.291932 2.444679-7.519254 8.943696-13.000082 16.768919-14.137998l283.508006-41.195238L493.099535 97.853655c3.498684-7.089465 10.720156-11.577686 18.627243-11.577686 7.907087 0 15.127536 4.489244 18.627243 11.577686l126.788661 256.904091 283.510052 41.195238c7.823176 1.137916 14.322194 6.618744 16.766872 14.137998 2.442632 7.519254 0.405229 15.773242-5.255701 21.291932L747.012502 631.354342l48.430012 282.363949c1.336438 7.7935-1.868557 15.667841-8.264221 20.314675-6.399757 4.646833-14.878872 5.257747-21.874193 1.582031L511.726777 802.298666 258.146385 935.614997C255.107165 937.211355 251.789607 938.000324 248.482281 938.000324zM130.422422 431.011454 313.25654 609.228415c4.894474 4.7727 7.128351 11.647271 5.974062 18.385742l-43.163055 251.64532 225.994104-118.811989c6.048763-3.180436 13.282514-3.180436 19.331277 0l225.992057 118.811989-43.163055-251.64532c-1.154289-6.738471 1.079588-13.613042 5.974062-18.385742l182.833095-178.216961-252.665557-36.71418c-6.767124-0.983397-12.614296-5.233188-15.641235-11.362792L511.726777 153.97893 398.729214 382.934482c-3.025916 6.129604-8.874111 10.379395-15.639189 11.362792L130.422422 431.011454z" fill="currentColor" p-id="6598"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764642248873" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12201" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M335.008 916.629333c-35.914667 22.314667-82.88 10.773333-104.693333-25.557333a77.333333 77.333333 0 0 1-8.96-57.429333l46.485333-198.24a13.141333 13.141333 0 0 0-4.021333-12.864l-152.16-132.586667c-31.605333-27.52-35.253333-75.648-8.234667-107.733333a75.68 75.68 0 0 1 51.733333-26.752L354.848 339.2c4.352-0.362667 8.245333-3.232 10.026667-7.594667l76.938666-188.170666c16.032-39.2 60.618667-57.92 99.52-41.461334a76.309333 76.309333 0 0 1 40.832 41.461334l76.938667 188.16c1.781333 4.373333 5.674667 7.253333 10.026667 7.605333l199.712 16.277333c41.877333 3.413333 72.885333 40.458667 69.568 82.517334a76.938667 76.938667 0 0 1-26.08 51.978666l-152.16 132.586667c-3.541333 3.082667-5.141333 8.074667-4.021334 12.853333l46.485334 198.24c9.621333 41.013333-15.36 82.336-56.138667 92.224a75.285333 75.285333 0 0 1-57.525333-9.237333l-170.976-106.24a11.296 11.296 0 0 0-12.010667 0l-170.986667 106.24z" fill="#3b82f6" p-id="12202"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764643976711" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18770" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M853.333333 768c35.413333 0 64-20.650667 64-55.978667V170.581333A63.978667 63.978667 0 0 0 853.333333 106.666667H170.666667C135.253333 106.666667 106.666667 135.253333 106.666667 170.581333v541.44C106.666667 747.285333 135.338667 768 170.666667 768h201.173333l110.016 117.44a42.752 42.752 0 0 0 60.586667 0.042667L651.904 768H853.333333z m-219.029333-42.666667h-6.250667l-115.797333 129.962667c-0.106667 0.106667-116.010667-129.962667-116.010667-129.962667H170.666667c-11.776 0-21.333333-1.621333-21.333334-13.312V170.581333A21.205333 21.205333 0 0 1 170.666667 149.333333h682.666666c11.776 0 21.333333 9.536 21.333334 21.248v541.44c0 11.754667-9.472 13.312-21.333334 13.312H634.304zM341.333333 490.666667a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z m170.666667 0a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z m170.666667 0a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z" fill="currentColor" p-id="18771"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1763611281052" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7362" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M190.193225 471.411583c14.446014 0 26.139334-11.718903 26.139334-26.13831 0-14.44499-11.69332-26.164916-26.139334-26.164916-0.271176 0-0.490164 0.149403-0.73678 0.149403l-62.496379 0.146333c-1.425466-0.195451-2.90005-0.295735-4.373611-0.295735-19.677155 0-35.621289 16.141632-35.621289 36.114522L86.622358 888.550075c0 19.949354 15.96767 35.597753 35.670407 35.597753 1.916653 0 3.808746 0.292666 5.649674 0l61.022819 0.022513c0.099261 0 0.148379 0.048095 0.24764 0.048095 0.097214 0 0.146333-0.048095 0.24457-0.048095l0.73678 0 0-0.148379c13.413498-0.540306 24.174586-11.422144 24.174586-24.960485 0-13.55983-10.760065-24.441669-24.174586-24.981974l0-0.393973-50.949392 0 1.450025-402.275993L190.193225 471.409536z" fill="#606266" p-id="7363"></path><path d="M926.52241 433.948343c-19.283182-31.445176-47.339168-44.172035-81.289398-45.546336-1.77032-0.246617-3.536546-0.39295-5.380544-0.39295l-205.447139-0.688685c13.462616-39.059598 22.698978-85.58933 22.698978-129.317251 0-28.349675-3.193739-55.962569-9.041934-82.542948l-0.490164 0.049119c-10.638291-46.578852-51.736315-81.31498-100.966553-81.31498-57.264215 0-95.466282 48.15065-95.466282 106.126063 0 3.241834-0.294712 6.387477 0 9.532097-2.996241 108.386546-91.240027 195.548698-196.23636 207.513194l0 54.881958-0.785899 222.227314 0 229.744521 10.709923 0 500.025271 0.222057 8.746198-0.243547c19.35686 0.049119 30.239721-4.817726 47.803749-16.116049 16.682961-10.761088 29.236881-25.50079 37.490869-42.156122 2.260483-3.341095 4.028757-7.075139 5.106298-11.20111l77.018118-344.324116c1.056052-4.053316 1.348718-8.181333 1.056052-12.160971C943.643346 476.446249 938.781618 453.944769 926.52241 433.948343zM893.82573 486.837924l-82.983993 367.783411-0.099261-0.049119c-2.555196 6.141884-6.879688 11.596106-12.872169 15.427364-4.177136 2.727111-8.773827 4.351098-13.414521 4.964058-1.49812-0.195451-3.046383 0-4.620227 0l-477.028511-0.540306-0.171915-407.408897c89.323375-40.266076 154.841577-79.670527 188.596356-173.661202 0.072655 0.024559 0.124843 0.049119 0.195451 0.072655 2.99931-9.137101 6.313799-20.73423 8.697079-33.164331 5.551436-29.185716 5.258771-58.123792 5.258771-58.123792-4.937452-37.98001 25.940812-52.965306 44.364417-52.965306 25.304316 0.860601 50.263777 33.656541 50.263777 52.326762 0 0 5.600555 27.563776 5.649674 57.190537 0.048095 37.366026-4.6673 56.847729-4.6673 56.847729l-0.466628 0c-5.872754 30.879288-16.214287 60.138682-30.464849 86.964654l0.36839 0.342808c-2.358721 4.815679-3.709485 10.220782-3.709485 15.943111 0 19.922748 19.088754 21.742187 38.765909 21.742187l238.761895 0.270153c0 0 14.666024 0.465604 14.690584 0.465604l0 0.100284c12.132318-0.638543 24.221658 5.207605 31.100322 16.409738 5.504364 9.016351 6.437619 19.6045 3.486404 28.988218L893.82573 486.837924z" fill="#606266" p-id="7364"></path><path d="M264.827039 924.31872c0.319272 0.024559 0.441045 0.024559 0.295735-0.024559 0.243547-0.048095 0.367367-0.074701-0.295735-0.074701s-0.539282 0.026606-0.271176 0.074701C264.43409 924.343279 264.532327 924.343279 264.827039 924.31872z" fill="#606266" p-id="7365"></path></svg> <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764643817261" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15130" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M190.193225 471.411583c14.446014 0 26.139334-11.718903 26.139334-26.13831 0-14.44499-11.69332-26.164916-26.139334-26.164916-0.271176 0-0.490164 0.149403-0.73678 0.149403l-62.496379 0.146333c-1.425466-0.195451-2.90005-0.295735-4.373611-0.295735-19.677155 0-35.621289 16.141632-35.621289 36.114522L86.622358 888.550075c0 19.949354 15.96767 35.597753 35.670407 35.597753 1.916653 0 3.808746 0.292666 5.649674 0l61.022819 0.022513c0.099261 0 0.148379 0.048095 0.24764 0.048095 0.097214 0 0.146333-0.048095 0.24457-0.048095l0.73678 0 0-0.148379c13.413498-0.540306 24.174586-11.422144 24.174586-24.960485 0-13.55983-10.760065-24.441669-24.174586-24.981974l0-0.393973-50.949392 0 1.450025-402.275993L190.193225 471.409536z" fill="currentColor" p-id="15131"></path><path d="M926.52241 433.948343c-19.283182-31.445176-47.339168-44.172035-81.289398-45.546336-1.77032-0.246617-3.536546-0.39295-5.380544-0.39295l-205.447139-0.688685c13.462616-39.059598 22.698978-85.58933 22.698978-129.317251 0-28.349675-3.193739-55.962569-9.041934-82.542948l-0.490164 0.049119c-10.638291-46.578852-51.736315-81.31498-100.966553-81.31498-57.264215 0-95.466282 48.15065-95.466282 106.126063 0 3.241834-0.294712 6.387477 0 9.532097-2.996241 108.386546-91.240027 195.548698-196.23636 207.513194l0 54.881958-0.785899 222.227314 0 229.744521 10.709923 0 500.025271 0.222057 8.746198-0.243547c19.35686 0.049119 30.239721-4.817726 47.803749-16.116049 16.682961-10.761088 29.236881-25.50079 37.490869-42.156122 2.260483-3.341095 4.028757-7.075139 5.106298-11.20111l77.018118-344.324116c1.056052-4.053316 1.348718-8.181333 1.056052-12.160971C943.643346 476.446249 938.781618 453.944769 926.52241 433.948343zM893.82573 486.837924l-82.983993 367.783411-0.099261-0.049119c-2.555196 6.141884-6.879688 11.596106-12.872169 15.427364-4.177136 2.727111-8.773827 4.351098-13.414521 4.964058-1.49812-0.195451-3.046383 0-4.620227 0l-477.028511-0.540306-0.171915-407.408897c89.323375-40.266076 154.841577-79.670527 188.596356-173.661202 0.072655 0.024559 0.124843 0.049119 0.195451 0.072655 2.99931-9.137101 6.313799-20.73423 8.697079-33.164331 5.551436-29.185716 5.258771-58.123792 5.258771-58.123792-4.937452-37.98001 25.940812-52.965306 44.364417-52.965306 25.304316 0.860601 50.263777 33.656541 50.263777 52.326762 0 0 5.600555 27.563776 5.649674 57.190537 0.048095 37.366026-4.6673 56.847729-4.6673 56.847729l-0.466628 0c-5.872754 30.879288-16.214287 60.138682-30.464849 86.964654l0.36839 0.342808c-2.358721 4.815679-3.709485 10.220782-3.709485 15.943111 0 19.922748 19.088754 21.742187 38.765909 21.742187l238.761895 0.270153c0 0 14.666024 0.465604 14.690584 0.465604l0 0.100284c12.132318-0.638543 24.221658 5.207605 31.100322 16.409738 5.504364 9.016351 6.437619 19.6045 3.486404 28.988218L893.82573 486.837924z" fill="currentColor" p-id="15132"></path><path d="M264.827039 924.31872c0.319272 0.024559 0.441045 0.024559 0.295735-0.024559 0.243547-0.048095 0.367367-0.074701-0.295735-0.074701s-0.539282 0.026606-0.271176 0.074701C264.43409 924.343279 264.532327 924.343279 264.827039 924.31872z" fill="currentColor" p-id="15133"></path></svg>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764641794770" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5469" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M64 483.04V872c0 37.216 30.144 67.36 67.36 67.36H192V416.32l-60.64-0.64A67.36 67.36 0 0 0 64 483.04zM857.28 344.992l-267.808 1.696c12.576-44.256 18.944-83.584 18.944-118.208 0-78.56-68.832-155.488-137.568-145.504-60.608 8.8-67.264 61.184-67.264 126.816v59.264c0 76.064-63.84 140.864-137.856 148L256 416.96v522.4h527.552a102.72 102.72 0 0 0 100.928-83.584l73.728-388.96a102.72 102.72 0 0 0-100.928-121.824z" p-id="5470" fill="#3b82f6"></path></svg>
\ No newline at end of file
<template>
<el-dropdown @command="handleMore" trigger="click">
<el-icon class="cursor-pointer"><IEpMore /></el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="举报">举报</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup lang="ts">
import { addComplaint } from '@/api'
import type { ArticleItemDto } from '@/api/article/types'
const { articleDetail } = defineProps<{
articleDetail: ArticleItemDto
}>()
const handleMore = async (command: string) => {
if (command === '举报') {
const { value } = await ElMessageBox.prompt('请输入举报原因', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^\S+$/,
inputPlaceholder: '请输入举报原因',
inputErrorMessage: '举报原因不能为空',
})
addComplaint({ articleId: articleDetail.id, reason: value })
ElMessage.success('举报成功')
}
}
</script>
...@@ -3,47 +3,80 @@ ...@@ -3,47 +3,80 @@
class="bg-white backdrop-blur-sm rounded-lg shadow-sm border border-white/50 overflow-hidden" class="bg-white backdrop-blur-sm rounded-lg shadow-sm border border-white/50 overflow-hidden"
> >
<!-- 发布者信息 --> <!-- 发布者信息 -->
<div class="p-6 border-b border-gray-100"> <div class="p-6 border-b border-gray-100 pb-0">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<div class="relative"> <div class="relative">
<img <img
:src="articleDetail?.createUserAvatar" :src="articleDetail?.createUserAvatar"
alt="" alt=""
class="w-12 h-12 rounded-full object-cover" class="w-12 h-12 rounded-full object-cover cursor-pointer"
@click="
jumpToUserHomePage({
userId: articleDetail?.createUserId,
isReal:
articleDetail.type === 'practice' || articleDetail.type === 'interview' ? 1 : 0,
})
"
/> />
<div <!-- <div
class="absolute -bottom-1 -right-1 w-6 h-6 bg-gradient-to-r from-yellow-400 to-orange-400 rounded-full flex items-center justify-center text-xs font-bold text-white" class="absolute -bottom-1 -right-1 w-6 h-6 bg-gradient-to-r from-yellow-400 to-orange-400 rounded-full flex items-center justify-center text-xs font-bold text-white"
> >
8 8
</div> </div> -->
</div> </div>
<div class="flex-1"> <div class="flex-1">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h3 class="font-semibold text-gray-800">{{ articleDetail?.createUserName }}</h3> <h3 class="font-semibold text-gray-800">{{ articleDetail?.createUserName }}</h3>
<span <!-- <span
class="px-2 py-0.5 text-xs bg-gradient-to-r from-purple-100 to-blue-100 text-purple-600 rounded-full" class="px-2 py-0.5 text-xs bg-gradient-to-r from-purple-100 to-blue-100 text-purple-600 rounded-full"
> >
资深前端工程师 资深前端工程师
</span> </span> -->
</div> </div>
<p class="text-sm text-gray-500 mt-1"> <p class="text-sm text-gray-500 mt-1">
{{ dayjs((articleDetail?.createTime || 0) * 1000).format('YYYY-MM-DD HH:mm:ss') }} {{ dayjs((articleDetail?.createTime || 0) * 1000).format('YYYY-MM-DD HH:mm:ss') }}
· {{ articleDetail?.viewCount || 0 }} 阅读 · {{ articleDetail?.viewCount || 0 }} 阅读
</p> </p>
</div> </div>
<el-button type="primary" link>{{ articleType }}</el-button> <!-- 再次编辑按钮 -->
<el-link
v-if="isAuthor"
type="primary"
:underline="false"
@click="router.push(`/publishLongArticle/${articleDetail.type}?id=${articleDetail.id}`)"
class="text-sm"
>
编辑
</el-link>
<!-- 优化后的右侧内容 -->
<div class="flex items-center gap-3">
<span
class="px-3 py-1.5 text-sm font-medium bg-blue-50 text-blue-600 rounded-md hover:bg-blue-100 transition-colors"
v-if="articleDetail.relateColumn"
>
{{ articleDetail.relateColumn }}
</span>
<span
class="px-3 py-1.5 text-sm font-medium bg-blue-50 text-blue-600 rounded-md hover:bg-blue-100 transition-colors"
>
{{ articleType }}
</span>
<ActionMore :articleDetail="articleDetail" />
</div>
</div> </div>
</div> </div>
<!-- 帖子内容 -->
<div class="p-6"> <div class="p-6">
<h1 class="text-2xl font-bold text-gray-900 mb-4 leading-tight"> <h1 class="text-2xl font-bold text-gray-900 mb-4 leading-tight">
{{ articleDetail?.title }} {{ articleDetail?.title }}
</h1> </h1>
<!-- 文章内容 --> <!-- 文章内容 -->
<div class="prose prose-lg max-w-none"> <div v-if="!isHtml" class="prose prose-lg max-w-none">
<div class="text-gray-700 leading-relaxed space-y-4"> <div class="text-gray-700 leading-relaxed space-y-4 whitespace-pre-line text-17px">
{{ articleDetail?.content }} {{ articleDetail?.content }}
</div> </div>
...@@ -67,6 +100,8 @@ ...@@ -67,6 +100,8 @@
</div> </div>
</div> </div>
<div v-else v-html="articleDetail.content" class="html-content" v-image-preview="1111" />
<!-- 标签 --> <!-- 标签 -->
<div class="flex flex-wrap gap-2 mt-6"> <div class="flex flex-wrap gap-2 mt-6">
<span <span
...@@ -78,18 +113,76 @@ ...@@ -78,18 +113,76 @@
</span> </span>
</div> </div>
</div> </div>
<!-- 富文本内容的 图片预览 -->
<el-image-viewer
v-if="showPreview"
:url-list="srcList"
show-progress
:teleported="true"
:initial-index="currentPreviewIndex"
@close="showPreview = false"
/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { type ArticleItemDto } from '@/api' import type { ArticleItemDto } from '@/api'
import { articleTypeListOptions, ArticleTypeEnum } from '@/constants' import { articleTypeListOptions, ArticleTypeEnum } from '@/constants'
import ActionMore from '@/components/common/ActionMore/index.vue'
import { jumpToUserHomePage } from '@/utils'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
import type { Directive } from 'vue'
const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore)
const router = useRouter()
const { articleDetail } = defineProps<{ const { articleDetail } = defineProps<{
articleDetail: ArticleItemDto articleDetail: ArticleItemDto
isAudit: boolean // 是否是审核页面
}>() }>()
const articleType = computed(() => { const articleType = computed(() => {
return articleTypeListOptions.find((item) => item.value === articleDetail.type)?.label return articleTypeListOptions.find((item) => item.value === articleDetail.type)?.label
}) })
// 是否是作者
const isAuthor = computed(() => {
return articleDetail.createUserId === userInfo.value.userId
})
// 如果类型是帖子 专栏 和 专访 就是html
const isHtml = computed(() => {
return (
articleDetail.type === ArticleTypeEnum.POST ||
articleDetail.type === ArticleTypeEnum.COLUMN ||
articleDetail.type === ArticleTypeEnum.INTERVIEW
)
})
const showPreview = ref(false)
const currentPreviewIndex = ref(0)
const srcList = ref<string[]>([])
// 图片预览的指令
const vImagePreview: Directive = {
mounted(el: HTMLElement) {
const imgs = el.querySelectorAll('img')
imgs.forEach((item, index) => {
item.style.cursor = 'pointer'
if (item.src) {
srcList.value.push(item.src)
item.addEventListener('click', () => {
currentPreviewIndex.value = index
showPreview.value = true
})
}
})
},
}
</script> </script>
<style scoped>
.html-content {
font-size: 18px;
}
</style>
<template>
<el-dialog
v-model="visible"
title="发表评论"
width="500px"
:before-close="handleClose"
top="30vh"
>
<div class="flex gap-3">
<!-- 用户头像 -->
<el-avatar :size="40" :src="userInfo.hiddenAvatar" />
<!-- 评论输入框 -->
<el-input
v-model="commentContent"
type="textarea"
:rows="4"
placeholder="写下你的评论..."
maxlength="500"
show-word-limit
class="flex-1"
/>
</div>
<template #footer>
<div class="flex justify-end gap-2">
<el-button @click="handleClose" class="rounded-lg">取消</el-button>
<el-button
type="primary"
@click="handleSubmit"
class="px-6 py-2 bg-blue-500 hover:bg-blue-600 rounded-lg text-white text-sm font-medium shadow-sm hover:shadow-md transition-all duration-200"
>发表</el-button
>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { useUserStore } from '@/stores'
import { storeToRefs } from 'pinia'
import { addComment } from '@/api'
const emit = defineEmits<{
(e: 'commentSuccess'): void
}>()
const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore)
// 弹窗显示状态
const visible = ref(false)
// 评论内容
const commentContent = ref('')
let articleId = 0
// 暴露 open 方法
const open = (id: number) => {
articleId = id
visible.value = true
commentContent.value = ''
}
// 关闭弹窗
const handleClose = () => {
visible.value = false
commentContent.value = ''
}
// 提交评论
const handleSubmit = async () => {
if (!commentContent.value.trim()) {
ElMessage.warning('请输入评论内容')
return
}
// TODO: 这里处理提交逻辑
await addComment({
articleId: articleId,
content: commentContent.value,
})
console.log('评论内容:', commentContent.value)
ElMessage.success('评论发表成功')
handleClose()
emit('commentSuccess')
}
// 暴露方法给父组件
defineExpose({
open,
})
</script>
<style scoped>
/* 如果需要额外样式可以在这里添加 */
</style>
<template>
<button class="btn" v-bind="attrs">测试111</button>
</template>
<script setup lang="ts">
// 二次封装el-button 除去el-button的默认样式 然后保留其他功能
import type { ButtonProps } from 'element-plus'
// const props = defineProps<ButtonProps>()
const attrs = useAttrs()
</script>
<style scoped>
.el-button {
/* 会作用到根元素 */
all: unset !important;
}
</style>
...@@ -2,10 +2,17 @@ ...@@ -2,10 +2,17 @@
<div class="bg-white p-6 mb-6 rounded-lg shadow-sm"> <div class="bg-white p-6 mb-6 rounded-lg shadow-sm">
<div class="flex-1 bg-white rounded-lg border border-gray-200"> <div class="flex-1 bg-white rounded-lg border border-gray-200">
<!-- 主输入区域 --> <!-- 主输入区域 -->
<div class="flex gap-3 mb-4 items-start"> <div class="flex gap-3 mb-2 items-start">
<!-- 用户头像 --> <!-- 用户头像 -->
<el-avatar :size="48" :src="userInfo.avatar" class="flex-shrink-0"> <el-avatar
<el-icon><User /></el-icon> :size="48"
:src="userAvatar"
class="flex-shrink-0"
@click="() => console.log(form)"
>
<el-icon>
<IEpUser />
</el-icon>
</el-avatar> </el-avatar>
<!-- 输入区域 --> <!-- 输入区域 -->
...@@ -24,14 +31,15 @@ ...@@ -24,14 +31,15 @@
<el-input <el-input
type="textarea" type="textarea"
:placeholder="textMap[type].content" :placeholder="textMap[type].content"
:rows="3" :rows="6"
:maxlength="500" :maxlength="maxLength"
show-word-limit
resize="none" resize="none"
class="main-textarea" class="main-textarea"
v-model="form.content" v-model="form.content"
/> />
<!-- 字符计数 --> <!-- 字符计数 -->
<div class="absolute bottom-3 right-3 text-xs text-gray-400">1/30</div> <!-- <div class="absolute bottom-3 right-3 text-xs text-gray-400">1/30</div> -->
</div> </div>
<!-- 标签内容 --> <!-- 标签内容 -->
<div class="mb-2"> <div class="mb-2">
...@@ -48,7 +56,12 @@ ...@@ -48,7 +56,12 @@
</div> </div>
</div> </div>
<!-- 图片相关 --> <!-- 图片相关 -->
<div v-if="form.imgUrl.length" class="flex flex-wrap gap-2"> <div
v-if="form.imgUrl.length"
class="flex flex-wrap gap-2 w-fit"
v-loading="uploadPercent > 0"
:element-loading-text="uploadPercent + '%'"
>
<!-- 删除图片 --> <!-- 删除图片 -->
<div <div
class="relative w-20 h-20 rounded-lg overflow-hidden group" class="relative w-20 h-20 rounded-lg overflow-hidden group"
...@@ -60,7 +73,7 @@ ...@@ -60,7 +73,7 @@
@click="handleDeleteImg(img)" @click="handleDeleteImg(img)"
> >
<el-icon class="text-white text-xs"> <el-icon class="text-white text-xs">
<Close /> <IEpClose />
</el-icon> </el-icon>
</div> </div>
<el-image <el-image
...@@ -77,25 +90,41 @@ ...@@ -77,25 +90,41 @@
<div class="flex items-center justify-between pl-15"> <div class="flex items-center justify-between pl-15">
<!-- 左侧工具按钮 --> <!-- 左侧工具按钮 -->
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<el-tooltip content="添加标签" placement="top"> <el-tooltip content="添加标签" placement="top" :visible="visibleTagTooltip">
<el-button <el-button
ref="tagButtonRef"
text text
class="w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg" class="w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
@click="handleAddTag" @click="handleAddTag"
@mouseenter="visibleTagTooltip = true"
@mouseleave="visibleTagTooltip = false"
> >
<el-icon size="18"><CollectionTag /></el-icon> <el-icon size="18">
<IEpCollectionTag />
</el-icon>
</el-button> </el-button>
</el-tooltip> </el-tooltip>
<!-- 隐藏上传文件的input --> <!-- 隐藏上传文件的input -->
<input type="file" class="hidden" ref="fileInputRef" @change="handleFileChange" /> <input
type="file"
class="hidden"
ref="fileInputRef"
@change="handleFileChange"
accept="image/*"
multiple
/>
<el-tooltip content="添加图片" placement="top"> <el-tooltip content="添加图片" placement="top">
<el-button <el-button
text text
class="w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg" class="w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
@click="fileInputRef?.click()" @click="fileInputRef?.click()"
:disabled="uploadPercent > 0"
> >
<el-icon size="18"><Picture /></el-icon> <el-icon size="18">
<span v-if="!form.imgUrl.length && uploadPercent > 0"> <IEpLoading /></span>
<span v-else> <IEpPicture /></span>
</el-icon>
</el-button> </el-button>
</el-tooltip> </el-tooltip>
...@@ -107,7 +136,7 @@ ...@@ -107,7 +136,7 @@
<el-icon size="18"><VideoPlay /></el-icon> <el-icon size="18"><VideoPlay /></el-icon>
</el-button> </el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="添加附件" placement="top"> <el-tooltip content="添加附件" placement="top">
<el-button <el-button
text text
...@@ -129,6 +158,7 @@ ...@@ -129,6 +158,7 @@
<el-button <el-button
type="primary" type="primary"
:disabled="disabledSubmit"
class="px-6 py-2 bg-blue-500 hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed rounded-lg text-white text-sm font-medium shadow-sm hover:shadow-md transition-all duration-200" class="px-6 py-2 bg-blue-500 hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed rounded-lg text-white text-sm font-medium shadow-sm hover:shadow-md transition-all duration-200"
@click="handlePublish(ReleaseStatusTypeEnum.PUBLISH)" @click="handlePublish(ReleaseStatusTypeEnum.PUBLISH)"
> >
...@@ -142,6 +172,10 @@ ...@@ -142,6 +172,10 @@
v-model:tagList="form.tagList" v-model:tagList="form.tagList"
ref="selectTagsDialogRef" ref="selectTagsDialogRef"
/> />
<el-tour v-model="openTour" :mask="false" placement="left-start" type="primary">
<el-tour-step :target="tagButtonRef?.$el" description="在这里选择标签" placement="top" />
</el-tour>
</div> </div>
</template> </template>
<!-- 发布实践 或者 问吧 的 发布框 --> <!-- 发布实践 或者 问吧 的 发布框 -->
...@@ -149,18 +183,25 @@ ...@@ -149,18 +183,25 @@
import { useUserStore } from '@/stores' import { useUserStore } from '@/stores'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import SelectTagsDialog from './components/selectTagsDialog.vue' import SelectTagsDialog from './components/selectTagsDialog.vue'
import { useResetData } from '@/hooks' import { useResetData, useUploadImg } from '@/hooks'
import { ArticleTypeEnum, ReleaseStatusTypeEnum, SendTypeEnum } from '@/constants' import { ArticleTypeEnum, ReleaseStatusTypeEnum, SendTypeEnum } from '@/constants'
import { useTagsStore } from '@/stores' import { useTagsStore } from '@/stores'
import { uploadFile } from '@/api'
import { Close } from '@element-plus/icons-vue'
import { addOrUpdatePractice, addOrUpdateArticle } from '@/api' import { addOrUpdatePractice, addOrUpdateArticle } from '@/api'
import type { AddOrUpdatePracticeDto } from '@/api/practice/types' import type { AddOrUpdatePracticeDto } from '@/api'
import type { BooleanFlag } from '@/constants'
import type { ElButton } from 'element-plus'
import { useAnimate } from '@vueuse/core'
type ArticleType = ArticleTypeEnum.QUESTION | ArticleTypeEnum.PRACTICE type ArticleType = ArticleTypeEnum.QUESTION | ArticleTypeEnum.PRACTICE
const { type } = defineProps<{ const {
type,
isReal,
maxLength = 500,
} = defineProps<{
type: ArticleType type: ArticleType
isReal: BooleanFlag
maxLength?: number
}>() }>()
const textMap: Record< const textMap: Record<
...@@ -184,16 +225,42 @@ const { tagList } = storeToRefs(tagsStore) ...@@ -184,16 +225,42 @@ const { tagList } = storeToRefs(tagsStore)
const userStore = useUserStore() const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore) const { userInfo } = storeToRefs(userStore)
const userAvatar = computed(() => (isReal ? userInfo.value.avatar : userInfo.value.hiddenAvatar))
const selectTagsDialogRef = const selectTagsDialogRef =
useTemplateRef<InstanceType<typeof SelectTagsDialog>>('selectTagsDialogRef') useTemplateRef<InstanceType<typeof SelectTagsDialog>>('selectTagsDialogRef')
const fileInputRef = useTemplateRef<HTMLInputElement>('fileInputRef') const fileInputRef = useTemplateRef<HTMLInputElement>('fileInputRef')
const openTour = ref(false)
const disabledSubmit = computed(() => {
return !form.value.title || !form.value.content
})
const tagButtonRef = useTemplateRef<InstanceType<typeof ElButton>>('tagButtonRef')
const tagButtonEl = computed(() => tagButtonRef.value?.$el)
const visibleTagTooltip = ref(false)
const DURATION = 500
const { play } = useAnimate(
tagButtonEl,
[
{ transform: 'translateY(0)' },
{ transform: 'translateY(-10px)' },
{ transform: 'translateY(0)' },
],
{
duration: DURATION,
easing: 'ease-in-out',
immediate: false,
},
)
const [form, resetForm] = useResetData({ const [form, resetForm] = useResetData({
title: '', title: '',
content: '', content: '',
imgUrl: [], imgUrl: [] as string[],
releaseStatus: ReleaseStatusTypeEnum.PUBLISH, releaseStatus: ReleaseStatusTypeEnum.PUBLISH,
mainTagId: '', mainTagId: '',
tagList: [], tagList: [],
...@@ -201,6 +268,10 @@ const [form, resetForm] = useResetData({ ...@@ -201,6 +268,10 @@ const [form, resetForm] = useResetData({
sendTime: '', sendTime: '',
}) })
const { imgsStr, handleFileChange, handleDeleteImg, uploadPercent } = useUploadImg(
toRef(form.value, 'imgUrl'),
)
const mainTagText = computed(() => { const mainTagText = computed(() => {
return tagList.value.find((tag) => tag.id === Number(form.value.mainTagId))?.title return tagList.value.find((tag) => tag.id === Number(form.value.mainTagId))?.title
}) })
...@@ -213,29 +284,15 @@ const handleAddTag = () => { ...@@ -213,29 +284,15 @@ const handleAddTag = () => {
selectTagsDialogRef.value?.open() selectTagsDialogRef.value?.open()
} }
const handleFileChange = async (e: Event) => {
const file = (e.target as HTMLInputElement).files?.[0]
if (file) {
const { data } = await uploadFile(file)
form.value.imgUrl.push(data.data[0].filePath)
}
}
const handleDeleteImg = (img: string) => {
form.value.imgUrl = form.value.imgUrl.filter((item) => item !== img)
}
const validateForm = () => { const validateForm = () => {
if (!form.value.title) {
ElMessage.error('请输入实践标题')
return false
}
if (!form.value.content) {
ElMessage.error('请输入实践内容')
return false
}
if (!form.value.mainTagId) { if (!form.value.mainTagId) {
ElMessage.error('请选择主标签') ElMessage.warning({
message: '请选择主标签',
offset: 200,
})
play()
visibleTagTooltip.value = true
return false return false
} }
...@@ -246,7 +303,7 @@ const transformForm = (releaseStatus: ReleaseStatusTypeEnum): AddOrUpdatePractic ...@@ -246,7 +303,7 @@ const transformForm = (releaseStatus: ReleaseStatusTypeEnum): AddOrUpdatePractic
...form.value, ...form.value,
releaseStatus, releaseStatus,
faceUrl: form.value.imgUrl[0] || '', faceUrl: form.value.imgUrl[0] || '',
imgUrl: form.value.imgUrl.join(','), imgUrl: imgsStr.value,
tagList: [form.value.mainTagId, ...form.value.tagList].map((item, index) => ({ tagList: [form.value.mainTagId, ...form.value.tagList].map((item, index) => ({
sort: index, sort: index,
tagId: Number(item), tagId: Number(item),
......
...@@ -65,13 +65,17 @@ import { useTagsStore } from '@/stores/tags' ...@@ -65,13 +65,17 @@ import { useTagsStore } from '@/stores/tags'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import type { TagItemDto } from '@/api/tag/types' import type { TagItemDto } from '@/api/tag/types'
import type { SelectTagProps } from './types' import type { SelectTagProps } from './types'
const { maxSelectedTags = 1, filterTagsFn } = defineProps<SelectTagProps>() const { maxSelectedTags = 1, filterTagsFn, tagType = 'culture' } = defineProps<SelectTagProps>()
const emit = defineEmits<{ const emit = defineEmits<{
selected: [tag?: TagItemDto] selected: [tag?: TagItemDto]
}>() }>()
const tagsStore = useTagsStore() const tagsStore = useTagsStore()
const { tagList } = storeToRefs(tagsStore) const { tagList: cultureTagList, relatedScenariosTagList } = storeToRefs(tagsStore)
const tagList = computed<TagItemDto[]>(() =>
tagType === 'culture' ? cultureTagList.value : relatedScenariosTagList.value,
)
const filterTags = computed(() => { const filterTags = computed(() => {
if (filterTagsFn) { if (filterTagsFn) {
...@@ -155,10 +159,4 @@ const toggleTag = (tagId: number) => { ...@@ -155,10 +159,4 @@ const toggleTag = (tagId: number) => {
addTag(tagId) addTag(tagId)
} }
} }
// onMounted(() => {
// if (tagList.value.length === 0) {
// tagsStore.fetchTagList()
// }
// })
</script> </script>
...@@ -3,4 +3,5 @@ import type { TagItemDto } from '@/api/tag/types' ...@@ -3,4 +3,5 @@ import type { TagItemDto } from '@/api/tag/types'
export type SelectTagProps = { export type SelectTagProps = {
maxSelectedTags?: number maxSelectedTags?: number
filterTagsFn?: (tags: TagItemDto[]) => TagItemDto[] filterTagsFn?: (tags: TagItemDto[]) => TagItemDto[]
tagType?: 'culture' | 'related_scenarios'
} }
...@@ -13,13 +13,14 @@ ...@@ -13,13 +13,14 @@
:multiple="multiple" :multiple="multiple"
:limit="limit" :limit="limit"
class="custom-upload" class="custom-upload"
v-loading="uploadPercent > 0"
:element-loading-text="uploadPercent + '%'"
> >
<el-icon><Plus /></el-icon> <el-icon><Plus /></el-icon>
<el-dialog :append-to-body="true" v-model="dialogVisible">
<img class="w-full" :src="dialogImageUrl" alt="Preview Image" />
</el-dialog>
</el-upload> </el-upload>
<el-dialog v-model="dialogVisible">
<img w-full :src="dialogImageUrl" alt="Preview Image" />
</el-dialog>
</template> </template>
<script lang="ts" setup generic="T extends string | string[]"> <script lang="ts" setup generic="T extends string | string[]">
...@@ -41,14 +42,13 @@ const fileList = ref<UploadUserFile[]>([]) ...@@ -41,14 +42,13 @@ const fileList = ref<UploadUserFile[]>([])
const uploadRef = useTemplateRef('uploadRef') const uploadRef = useTemplateRef('uploadRef')
const dialogImageUrl = ref('') const dialogImageUrl = ref('')
const dialogVisible = ref(false) const dialogVisible = ref(false)
const uploadPercent = ref(0)
const isArrayType = computed(() => Array.isArray(modelValue.value)) const isArrayType = computed(() => Array.isArray(modelValue.value))
const hasReachedLimit = computed(() => fileList.value.length >= props.limit) const hasReachedLimit = computed(() => fileList.value.length >= props.limit)
const showUploadBtn = computed(() => (hasReachedLimit.value ? 'none' : 'flex')) const showUploadBtn = computed(() => (hasReachedLimit.value ? 'none' : 'flex'))
const isInternalUpdate = ref(false) const isInternalUpdate = ref(false)
const uploadProgress = ref(0)
const parseModelValueToUrls = (value: T): string[] => { const parseModelValueToUrls = (value: T): string[] => {
if (!value) return [] if (!value) return []
return Array.isArray(value) ? value.filter(Boolean) : (value as string).split(',').filter(Boolean) return Array.isArray(value) ? value.filter(Boolean) : (value as string).split(',').filter(Boolean)
...@@ -112,7 +112,6 @@ const handleExceed: UploadProps['onExceed'] = (uploadFiles) => { ...@@ -112,7 +112,6 @@ const handleExceed: UploadProps['onExceed'] = (uploadFiles) => {
const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) => { const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) => {
console.log('uploadFiles', uploadFiles) console.log('uploadFiles', uploadFiles)
if (uploadFiles.length > props.limit) { if (uploadFiles.length > props.limit) {
debugger
ElMessage.error(`最多上传 ${props.limit} 个文件`) ElMessage.error(`最多上传 ${props.limit} 个文件`)
const index = fileList.value.findIndex((file) => file.uid === uploadFile.uid) const index = fileList.value.findIndex((file) => file.uid === uploadFile.uid)
if (index !== -1) { if (index !== -1) {
...@@ -127,15 +126,21 @@ const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) => ...@@ -127,15 +126,21 @@ const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) =>
try { try {
let fileIndex = fileList.value.findIndex((file) => file.uid === uid) let fileIndex = fileList.value.findIndex((file) => file.uid === uid)
if (fileIndex !== -1) { if (fileIndex !== -1) {
fileList.value[fileIndex].status = 'uploading' fileList.value[fileIndex]!.status = 'uploading'
} }
const { data } = await uploadFileApi(uploadFile.raw, (progress) => { const { promise } = uploadFileApi(uploadFile.raw, {
console.log('progress', progress) onProgress: (progress) => {
console.log('progress', progress)
uploadPercent.value = progress
},
}) })
const data = await promise
console.log('data', data) console.log('data', data)
const url = data.fileUrl || data.data[0].filePath const url = data.filePath || ''
const name = data.fileName || data.data[0].finalName const name = data.finalName || ''
fileIndex = fileList.value.findIndex((file) => file.uid === uid) fileIndex = fileList.value.findIndex((file) => file.uid === uid)
...@@ -158,11 +163,13 @@ const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) => ...@@ -158,11 +163,13 @@ const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) =>
if (fileIndex !== -1) { if (fileIndex !== -1) {
fileList.value.splice(fileIndex, 1) fileList.value.splice(fileIndex, 1)
} }
} finally {
uploadPercent.value = 0
} }
} }
} }
const handleBeforeRemove: UploadProps['beforeRemove'] = (uploadFile) => { const handleBeforeRemove: UploadProps['beforeRemove'] = () => {
return ElMessageBox.confirm('确定要删除这个文件吗?', '提示', { return ElMessageBox.confirm('确定要删除这个文件吗?', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
......
...@@ -13,24 +13,24 @@ ...@@ -13,24 +13,24 @@
class="upload-area" class="upload-area"
> >
<div v-if="!uploading && !videoInfo" class="upload-placeholder"> <div v-if="!uploading && !videoInfo" class="upload-placeholder">
<el-icon class="upload-icon"><VideoCamera /></el-icon> <el-icon class="upload-icon"><IEpVideoCamera /></el-icon>
<div class="upload-text"> <div class="upload-text">
<p>拖拽视频文件到此处,或点击选择文件</p> <p>拖拽视频文件到此处,或点击选择文件</p>
<p class="upload-hint">支持 MP4、AVI、MOV 格式,文件大小不超过 500MB</p> <p class="upload-hint">支持 MP4、AVI、MOV 格式,文件大小不超过 {{ maxSize }}MB</p>
</div> </div>
</div> </div>
<!-- 上传进度 --> <!-- 上传进度 -->
<div v-if="uploading" class="upload-progress"> <div v-if="uploading" class="upload-progress">
<div class="progress-info"> <div class="progress-info">
<el-icon class="progress-icon"><VideoPlay /></el-icon> <el-icon class="progress-icon"><IEpVideoPlay /></el-icon>
<div class="progress-details"> <div class="progress-details">
<p class="file-name">{{ currentFile?.name }}</p> <p class="file-name">{{ currentFile?.name }}</p>
<p class="progress-text">正在上传...</p> <p class="progress-text">正在上传...</p>
</div> </div>
</div> </div>
<el-progress :percentage="uploadProgress" :stroke-width="8" class="progress-bar" /> <el-progress :percentage="uploadProgress" :stroke-width="8" class="progress-bar" />
<el-button type="danger" size="small" @click="cancelUpload" class="cancel-btn"> <el-button type="danger" size="small" @click.stop="cancelUpload" class="cancel-btn">
取消上传 取消上传
</el-button> </el-button>
</div> </div>
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
<div class="video-preview"> <div class="video-preview">
<video <video
:src="videoInfo.url" :src="videoInfo.url"
poster="http://soundasia.oss-cn-shenzhen.aliyuncs.com/colleagueBar/jpg/2024/01/08/1704677467931.jpg" poster="@/assets/img/culture/ask.png"
class="video-thumbnail" class="video-thumbnail"
muted muted
></video> ></video>
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
<!-- 错误提示 --> <!-- 错误提示 -->
<div v-if="uploadError" class="upload-error"> <div v-if="uploadError" class="upload-error">
<el-icon class="error-icon"><Warning /></el-icon> <el-icon class="error-icon"><IEpWarning /></el-icon>
<span>{{ uploadError }}</span> <span>{{ uploadError }}</span>
<el-button type="text" @click="retryUpload">重试</el-button> <el-button type="text" @click="retryUpload">重试</el-button>
</div> </div>
...@@ -82,7 +82,7 @@ interface VideoInfo { ...@@ -82,7 +82,7 @@ interface VideoInfo {
fileId?: string fileId?: string
} }
const { maxSize = 500, acceptFormats = ['mp4', 'avi', 'mov', 'wmv', 'flv'] } = const { maxSize = 1000, acceptFormats = ['mp4', 'avi', 'mov', 'wmv', 'flv'] } =
defineProps<UploadVideoProps>() defineProps<UploadVideoProps>()
const modelValue = defineModel<string>('modelValue', { required: true }) const modelValue = defineModel<string>('modelValue', { required: true })
...@@ -163,6 +163,7 @@ const handleFileChange = async (file: UploadFile) => { ...@@ -163,6 +163,7 @@ const handleFileChange = async (file: UploadFile) => {
await startUpload() await startUpload()
} }
let cancelUploadController = () => {}
// 开始上传 // 开始上传
const startUpload = async () => { const startUpload = async () => {
if (!currentFile.value) return if (!currentFile.value) return
...@@ -173,9 +174,14 @@ const startUpload = async () => { ...@@ -173,9 +174,14 @@ const startUpload = async () => {
uploadProgress.value = 0 uploadProgress.value = 0
// 调用你的上传方法 // 调用你的上传方法
const { data } = await uploadFileApi(currentFile.value, (progress) => {
uploadProgress.value = progress const { promise, cancel } = uploadFileApi(currentFile.value, {
onProgress: (progress) => {
uploadProgress.value = progress
},
}) })
cancelUploadController = cancel
const data = await promise
console.log(data) console.log(data)
// 获取视频元数据 // 获取视频元数据
...@@ -183,15 +189,15 @@ const startUpload = async () => { ...@@ -183,15 +189,15 @@ const startUpload = async () => {
// 根据你的 API 返回结构调整 // 根据你的 API 返回结构调整
const videoData: VideoInfo = { const videoData: VideoInfo = {
// url: data.fileUrl, url: data.filePath || '',
// 暂时写死 // 暂时写死
url: 'https://soundasia.oss-cn-shenzhen.aliyuncs.com/OA/readName/mp4/2025/11/12/Common/1762918987602.mp4', // url: 'https://soundasia.oss-cn-shenzhen.aliyuncs.com/OA/readName/mp4/2025/11/12/Common/1762918987602.mp4',
name: currentFile.value.name, name: currentFile.value.name,
size: currentFile.value.size, size: currentFile.value.size,
duration: metadata.duration, duration: metadata.duration,
resolution: metadata.resolution, resolution: metadata.resolution,
poster: data.fileUrl, poster: data.filePath || '',
fileId: data.fileId, fileId: data.fileId || '',
} }
videoInfo.value = videoData videoInfo.value = videoData
...@@ -215,6 +221,7 @@ const startUpload = async () => { ...@@ -215,6 +221,7 @@ const startUpload = async () => {
// 取消上传 // 取消上传
const cancelUpload = () => { const cancelUpload = () => {
cancelUploadController()
uploading.value = false uploading.value = false
uploadProgress.value = 0 uploadProgress.value = 0
ElMessage.info('已取消上传') ElMessage.info('已取消上传')
...@@ -233,6 +240,20 @@ const retryUpload = () => { ...@@ -233,6 +240,20 @@ const retryUpload = () => {
uploadError.value = '' uploadError.value = ''
startUpload() startUpload()
} }
// reset
const reset = () => {
uploadRef.value = null
uploading.value = false
uploadProgress.value = 0
uploadError.value = ''
currentFile.value = null
videoInfo.value = null
modelValue.value = ''
}
defineExpose({
reset,
})
</script> </script>
<style scoped> <style scoped>
...@@ -358,7 +379,7 @@ const retryUpload = () => { ...@@ -358,7 +379,7 @@ const retryUpload = () => {
height: 80px; height: 80px;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
background: #000; /* background: #000; */
} }
.video-thumbnail { .video-thumbnail {
......
<template>
<div style="border: 1px solid #ccc" class="h-full">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
<Editor
style="height: 500px; overflow-y: hidden"
v-model="valueHtml"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="handleCreated"
/>
</div>
</template>
<script setup lang="ts">
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { uploadFile } from '@/api'
const mode = 'default'
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef()
// 内容 HTML
const valueHtml = defineModel<string>()
// 去掉上传视频的功能
const toolbarConfig = {
excludeKeys: ['group-video', 'group-more-video', 'group-more-video'],
}
// 去掉上传视频
const editorConfig = {
placeholder: '请输入内容...',
MENU_CONF: {
uploadImage: {
customUpload: async (file: File, insertFn: (url: string) => void) => {
const { promise } = uploadFile(file)
const data = await promise
insertFn(data.filePath)
},
},
},
}
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
const handleCreated = (editor: typeof Editor) => {
editorRef.value = editor // 记录 editor 实例,重要!
}
</script>
<style scoped>
/* 使用 :deep() 来穿透 scoped 样式的限制, 直接为编辑器内容区域设置样式。这里的 p 标签是内容的最小单位,为其设置样式最精确。 */
:deep(.w-e-text-container p) {
font-size: 18px !important;
}
</style>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>
...@@ -10,27 +10,24 @@ interface IConfig { ...@@ -10,27 +10,24 @@ interface IConfig {
export const app_config: { [key: string]: IConfig } = { export const app_config: { [key: string]: IConfig } = {
// 正式环境 // 正式环境
production: { production: {
baseUrl: 'https://culture.yswg.com.cn:8089', baseUrl: 'http://culture.yswg.com.cn:8089',
loginType: 3, loginType: 1, // 3
wxRedirect: 'oa3.yswg.com.cn', wxRedirect: 'culture.yswg.com.cn:3000',
}, },
// 测试环境 暂时无测试环境部署 // 测试环境 暂时无测试环境部署
test: { test: {
baseUrl: 'http://192.168.2.55:8089', // 首拥本地 baseUrl: 'http://192.168.2.55:8089', // 首拥本地
loginType: 1, loginType: 1,
wxRedirect: 'oatest.yswg.com.cn:3457', wxRedirect: '',
}, },
// 开发环境 // 开发环境
development: { development: {
// baseUrl: 'http://oa.yswg.com.cn:8082', // 正式环境 baseUrl: 'http://culture.yswg.com.cn:8089', // 线上测试机
baseUrl: '/api1', // 线上测试机 // baseUrl: 'http://192.168.2.168:8089', // 立鹏本地/
// baseUrl: 'http://192.168.2.110:8082', // 线上测试机 // baseUrl: 'http://192.168.2.55:8089', // 首拥本地
// baseUrl: 'http://192.168.2.85:8080', // 洋倍
// baseUrl: 'http://192.168.2.12:8084', // 立鹏
// baseUrl: 'http://192.168.2.55:8089', // 首拥
loginType: 1, loginType: 1,
wxRedirect: 'oatest.yswg.com.cn:3457', wxRedirect: '',
}, },
} }
...@@ -89,3 +89,11 @@ export enum AuditStatusEnum { ...@@ -89,3 +89,11 @@ export enum AuditStatusEnum {
// 驳回审核 // 驳回审核
REJECTED = 2, REJECTED = 2,
} }
// 使用状态枚举
export enum UsageStatusEnum {
// 待使用
UNUSABLE = 0,
// 已使用
USED = 1,
}
import { ArticleTypeEnum, AuditStatusEnum, CommentTypeEnum, TaskTypeEnum } from './enums' import {
ArticleTypeEnum,
AuditStatusEnum,
CommentTypeEnum,
TaskTypeEnum,
UsageStatusEnum,
} from './enums'
// 地区列表 // 地区列表
export const regionListOptions = [ export const regionListOptions = [
...@@ -43,7 +49,7 @@ export const articleTypeListOptions: { label: string; value: ArticleTypeEnum }[] ...@@ -43,7 +49,7 @@ export const articleTypeListOptions: { label: string; value: ArticleTypeEnum }[]
value: ArticleTypeEnum.VIDEO, value: ArticleTypeEnum.VIDEO,
}, },
{ {
label: '问', label: '问',
value: ArticleTypeEnum.QUESTION, value: ArticleTypeEnum.QUESTION,
}, },
{ {
...@@ -60,6 +66,36 @@ export const articleTypeListOptions: { label: string; value: ArticleTypeEnum }[] ...@@ -60,6 +66,36 @@ export const articleTypeListOptions: { label: string; value: ArticleTypeEnum }[]
}, },
] ]
export const articleTypeListOptionsForReal: { label: string; value: ArticleTypeEnum }[] = [
{
label: '实践',
value: ArticleTypeEnum.PRACTICE,
},
{
label: '专访',
value: ArticleTypeEnum.INTERVIEW,
},
]
export const articleTypeListOptionsForNotReal: { label: string; value: ArticleTypeEnum }[] = [
{
label: '帖子',
value: ArticleTypeEnum.POST,
},
{
label: '视频',
value: ArticleTypeEnum.VIDEO,
},
{
label: '问吧',
value: ArticleTypeEnum.QUESTION,
},
{
label: '专栏',
value: ArticleTypeEnum.COLUMN,
},
]
// 审核类型列表 // 审核类型列表
export const auditTypeListOptions: { label: string; value: AuditStatusEnum }[] = [ export const auditTypeListOptions: { label: string; value: AuditStatusEnum }[] = [
{ {
...@@ -76,6 +112,18 @@ export const auditTypeListOptions: { label: string; value: AuditStatusEnum }[] = ...@@ -76,6 +112,18 @@ export const auditTypeListOptions: { label: string; value: AuditStatusEnum }[] =
}, },
] ]
// 使用状态列表
export const usageStatusListOptions: { label: string; value: UsageStatusEnum }[] = [
{
label: '待使用',
value: UsageStatusEnum.UNUSABLE,
},
{
label: '已使用',
value: UsageStatusEnum.USED,
},
]
// 任务类型列表 // 任务类型列表
export const taskTypeListOptions = [ export const taskTypeListOptions = [
{ {
......
import type { InjectionKey, Ref } from 'vue' import type { InjectionKey, Ref } from 'vue'
export const TABS_REF_KEY = Symbol() as InjectionKey<Ref<HTMLElement | null>> export const TABS_REF_KEY = Symbol('tabsRef') as InjectionKey<Ref<HTMLElement | null>>
export const COMMENT_REF_KEY = Symbol() as InjectionKey<Ref<HTMLElement | null>> export const COMMENT_REF_KEY = Symbol('commentRef') as InjectionKey<Ref<HTMLElement | null>>
export const IS_REAL_KEY = Symbol('isReal') as InjectionKey<Ref<number>>
...@@ -2,3 +2,4 @@ export * from './useResetData' ...@@ -2,3 +2,4 @@ export * from './useResetData'
export * from './usePageSearch' export * from './usePageSearch'
export * from './useScrollTop' export * from './useScrollTop'
export * from './useHintAnimation' export * from './useHintAnimation'
export * from './useUploadImg'
...@@ -7,7 +7,7 @@ export const useHintAnimation = ( ...@@ -7,7 +7,7 @@ export const useHintAnimation = (
el: MaybeRef<HTMLElement | null>, el: MaybeRef<HTMLElement | null>,
{ classes = [], duration = 200 }: UseHintAnimationOptions = {}, { classes = [], duration = 200 }: UseHintAnimationOptions = {},
) => { ) => {
let timer: number | null = null let timer: NodeJS.Timeout | null = null
const triggerAnimation = () => { const triggerAnimation = () => {
const dom = unref(el) const dom = unref(el)
if (!dom) return if (!dom) return
......
...@@ -109,6 +109,13 @@ export function usePageSearch< ...@@ -109,6 +109,13 @@ export function usePageSearch<
search() search()
} }
// 清空搜索数据和列表
const clear = () => {
list.value = []
total.value = 0
resetSearchParams()
}
if (immediate) { if (immediate) {
onMounted(search) onMounted(search)
} }
...@@ -123,5 +130,6 @@ export function usePageSearch< ...@@ -123,5 +130,6 @@ export function usePageSearch<
goToPage, goToPage,
changePageSize, changePageSize,
refresh, refresh,
clear,
} }
} }
...@@ -19,36 +19,66 @@ function ScrollTopComp(_: any, { emit }: SetupContext<Events>) { ...@@ -19,36 +19,66 @@ function ScrollTopComp(_: any, { emit }: SetupContext<Events>) {
) )
} }
// 顺便兼容下多个的
export const useScrollTop = ( export const useScrollTop = (
el: MaybeRef<HTMLElement | null | Window>, el: MaybeRef<HTMLElement | null | Window | HTMLElement[] | [Window]>,
options: { compatFixedHeader?: boolean } = {}, options: { compatFixedHeader?: boolean } = {},
) => { ) => {
const { compatFixedHeader = true } = options const { compatFixedHeader = true } = options
const handleBackTop = () => { const handleBackTop = (currentIndex: number = 0): Promise<void> => {
const dom = unref(el) return new Promise((resolve) => {
if (!dom) return const initDoms = unref(el)
if (!initDoms) {
if (dom instanceof Window) { resolve()
window.scrollTo({ return
top: 0, }
behavior: 'smooth',
}) let doms = []
return
} if (!Array.isArray(initDoms)) {
doms = [initDoms]
if (compatFixedHeader) { } else {
const top = dom.getBoundingClientRect().top + window.scrollY - 52 doms = initDoms
window.scrollTo({ }
top,
behavior: 'smooth', const finish = () => {
}) console.log('scrollend')
} else { resolve()
dom.scrollIntoView({ }
behavior: 'smooth',
block: 'start', // 手动添加一次scrollend事件
}) window.addEventListener('scrollend', finish, { once: true })
}
// 有时候在滚轮就在原位置 不会触发 scrollend事件 所以手动触发一次
setTimeout(() => {
window.removeEventListener('scrollend', finish)
resolve()
}, 1000)
// 下面会触发scrollend事件一次
const dom = doms[currentIndex] as HTMLElement | Window
if (dom instanceof Window) {
window.scrollTo({
top: 0,
behavior: 'smooth',
})
return
}
if (compatFixedHeader) {
const top = dom?.getBoundingClientRect?.().top + window.scrollY - 52
window.scrollTo({
top, // 可以设置滚动的距离
behavior: 'smooth',
})
} else {
dom?.scrollIntoView?.({
// 只能滚动到dom的顶部 不能设置滚动的距离
behavior: 'smooth',
block: 'start',
})
}
})
} }
return { return {
......
import { uploadFile } from '@/api'
// 默认参数
export const useUploadImg = (imgList: Ref<string[]> = ref([])) => {
const uploadPercent = ref(0)
// 字符串拼
const imgsStr = computed(() => imgList.value.join(','))
// 上传图片的change事件
const handleFileChange = async (e: Event) => {
try {
const file = (e.target as HTMLInputElement).files?.[0]
if (!file) return
const { promise } = uploadFile(file, {
onProgress: (progress) => {
uploadPercent.value = progress
},
})
const data = await promise
imgList.value.push(data.filePath)
} catch (error) {
console.error('上传失败:', error)
} finally {
uploadPercent.value = 0
// 重置input的value
;(e.target as HTMLInputElement).value = ''
}
}
// 删除图片
const handleDeleteImg = (urlStr: string) => {
imgList.value = imgList.value.filter((item) => item !== urlStr)
}
return {
imgsStr,
imgList,
handleFileChange,
uploadPercent,
handleDeleteImg,
}
}
...@@ -30,8 +30,8 @@ export default defineComponent((_, { expose }) => { ...@@ -30,8 +30,8 @@ export default defineComponent((_, { expose }) => {
}) })
const formRef = ref<InstanceType<typeof ElForm>>() const formRef = ref<InstanceType<typeof ElForm>>()
const rules = { const rules = {
title: [{ required: true, message: '请输入实践标题', trigger: 'blur' }], title: [{ required: true, message: '请输入专栏标题', trigger: 'blur' }],
content: [{ required: true, message: '请输入实践内容', trigger: 'blur' }], content: [{ required: true, message: '请输入专栏内容', trigger: 'blur' }],
releaseStatus: [{ required: true, message: '请选择发布时间', trigger: 'blur' }], releaseStatus: [{ required: true, message: '请选择发布时间', trigger: 'blur' }],
mainTagId: [{ required: true, message: '请选择主标签', trigger: 'blur' }], mainTagId: [{ required: true, message: '请选择主标签', trigger: 'blur' }],
sendType: [{ required: true, message: '请选择发布类型', trigger: 'blur' }], sendType: [{ required: true, message: '请选择发布类型', trigger: 'blur' }],
...@@ -40,6 +40,7 @@ export default defineComponent((_, { expose }) => { ...@@ -40,6 +40,7 @@ export default defineComponent((_, { expose }) => {
{ required: true, message: '请选择专栏栏目', trigger: 'blur', type: 'number', min: 1 }, { required: true, message: '请选择专栏栏目', trigger: 'blur', type: 'number', min: 1 },
], ],
isRecommend: [{ required: true, message: '请选择是否推荐', trigger: 'blur' }], isRecommend: [{ required: true, message: '请选择是否推荐', trigger: 'blur' }],
imgUrl: [{ required: true, message: '请上传图片', trigger: 'blur' }],
} }
const transformForm = (releaseStatus: ReleaseStatusTypeEnum): AddOrUpdateColumnDto => { const transformForm = (releaseStatus: ReleaseStatusTypeEnum): AddOrUpdateColumnDto => {
...@@ -103,7 +104,7 @@ export default defineComponent((_, { expose }) => { ...@@ -103,7 +104,7 @@ export default defineComponent((_, { expose }) => {
<el-input <el-input
v-model={form.value.content} v-model={form.value.content}
type="textarea" type="textarea"
placeholder="分享你的企业文化实践实例" placeholder="请输入专栏内容"
rows={6} rows={6}
maxlength={1000} maxlength={1000}
show-word-limit show-word-limit
......
...@@ -27,8 +27,8 @@ export default defineComponent((_, { expose }) => { ...@@ -27,8 +27,8 @@ export default defineComponent((_, { expose }) => {
}) })
const formRef = ref<InstanceType<typeof ElForm>>() const formRef = ref<InstanceType<typeof ElForm>>()
const rules = { const rules = {
title: [{ required: true, message: '请输入实践标题', trigger: 'blur' }], title: [{ required: true, message: '请输入专访标题', trigger: 'blur' }],
content: [{ required: true, message: '请输入实践内容', trigger: 'blur' }], content: [{ required: true, message: '请输入专访内容', trigger: 'blur' }],
releaseStatus: [{ required: true, message: '请选择发布时间', trigger: 'blur' }], releaseStatus: [{ required: true, message: '请选择发布时间', trigger: 'blur' }],
mainTagId: [{ required: true, message: '请选择主标签', trigger: 'blur' }], mainTagId: [{ required: true, message: '请选择主标签', trigger: 'blur' }],
sendType: [{ required: true, message: '请选择发布类型', trigger: 'blur' }], sendType: [{ required: true, message: '请选择发布类型', trigger: 'blur' }],
...@@ -37,6 +37,7 @@ export default defineComponent((_, { expose }) => { ...@@ -37,6 +37,7 @@ export default defineComponent((_, { expose }) => {
{ required: true, message: '请选择专访栏目', trigger: 'blur', type: 'number', min: 1 }, { required: true, message: '请选择专访栏目', trigger: 'blur', type: 'number', min: 1 },
], ],
isRecommend: [{ required: true, message: '请选择是否推荐', trigger: 'blur' }], isRecommend: [{ required: true, message: '请选择是否推荐', trigger: 'blur' }],
imgUrl: [{ required: true, message: '请上传图片', trigger: 'blur' }],
} }
const transformForm = (releaseStatus: ReleaseStatusTypeEnum): AddOrUpdateInterviewDto => { const transformForm = (releaseStatus: ReleaseStatusTypeEnum): AddOrUpdateInterviewDto => {
...@@ -100,7 +101,7 @@ export default defineComponent((_, { expose }) => { ...@@ -100,7 +101,7 @@ export default defineComponent((_, { expose }) => {
<el-input <el-input
v-model={form.value.content} v-model={form.value.content}
type="textarea" type="textarea"
placeholder="分享你的企业文化实践实例" placeholder="请输入专访内容"
rows={6} rows={6}
maxlength={1000} maxlength={1000}
show-word-limit show-word-limit
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
storage-type="session" storage-type="session"
class="fixed" class="fixed"
> >
<div class="cursor-pointer online-time flex flex-col items-center z-50"> <div class="cursor-pointer online-time flex flex-col items-center z-1050">
<!-- 图片容器 --> <!-- 图片容器 -->
<div <div
class="mb-4 w-24 h-24 bg-white rounded-full shadow-xl flex items-center justify-center hover:scale-110 transition-transform cursor-pointer" class="mb-4 w-24 h-24 bg-white rounded-full shadow-xl flex items-center justify-center hover:scale-110 transition-transform cursor-pointer"
...@@ -38,13 +38,16 @@ ...@@ -38,13 +38,16 @@
<div <div
class="text-center text-2xl font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent" class="text-center text-2xl font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent"
> >
00:00 {{ formatSeconds }}
</div> </div>
</div> </div>
<div class="absolute bottom-2 left-3 right-3 h-1 bg-gray-200 rounded-full overflow-hidden"> <div class="absolute bottom-2 left-3 right-3 h-1 bg-gray-200 rounded-full overflow-hidden">
<div <div
class="h-full bg-gradient-to-r from-blue-400 to-purple-400 rounded-full w-3/5 animate-pulse" class="h-full bg-gradient-to-r from-blue-400 to-purple-400 rounded-full animate-pulse"
:style="{
width: widthRate + '%',
}"
></div> ></div>
</div> </div>
...@@ -67,12 +70,54 @@ ...@@ -67,12 +70,54 @@
<script setup lang="ts"> <script setup lang="ts">
import { UseDraggable as Draggable } from '@vueuse/components' import { UseDraggable as Draggable } from '@vueuse/components'
import { useWindowSize } from '@vueuse/core' import { useWindowSize } from '@vueuse/core'
import { getTodayOnlineSeconds, heartbeat } from '@/api'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
dayjs.extend(utc)
const { width, height } = useWindowSize() const { height } = useWindowSize()
const CONTAINER_WIDTH = 130, const CONTAINER_HEIGHT = 170,
CONTAINER_HEIGHT = 170,
GAP = 30 GAP = 30
// CONTAINER_WIDTH = 130
const maxSeconds = 60 * 30 // 半小时
const x = width.value - CONTAINER_WIDTH - GAP const x = GAP
const y = height.value - CONTAINER_HEIGHT - GAP const y = height.value - CONTAINER_HEIGHT - GAP
const currentSeconds = ref(0)
// 进度条的比例
const widthRate = computed(() => {
if (currentSeconds.value >= maxSeconds) {
return 100
} else {
return (currentSeconds.value / maxSeconds) * 100
}
})
// 在线时长格式化 将秒级 格式化为 00:00
// 如果大于一个小时的话,则显示小时:分钟:秒
const formatSeconds = computed(() => {
if (currentSeconds.value >= 60 * 60) {
return dayjs.utc(currentSeconds.value * 1000).format('hh:mm:ss')
} else {
return dayjs.utc(currentSeconds.value * 1000).format('mm:ss')
}
})
onMounted(async () => {
const { data } = await getTodayOnlineSeconds()
heartbeat()
currentSeconds.value = data
})
const timer1 = setInterval(() => {
currentSeconds.value++
}, 1000)
const timer2 = setInterval(async () => {
heartbeat()
}, 1000 * 30)
onUnmounted(() => {
clearInterval(timer1)
clearInterval(timer2)
})
</script> </script>
...@@ -23,7 +23,6 @@ export default defineComponent((_, { expose }) => { ...@@ -23,7 +23,6 @@ export default defineComponent((_, { expose }) => {
const rules = { const rules = {
title: [{ required: true, message: '请输入实践标题', trigger: 'blur' }], title: [{ required: true, message: '请输入实践标题', trigger: 'blur' }],
content: [{ required: true, message: '请输入实践内容', trigger: 'blur' }], content: [{ required: true, message: '请输入实践内容', trigger: 'blur' }],
imgUrl: [{ required: true, message: '请上传实践图片', trigger: 'change' }],
releaseStatus: [{ required: true, message: '请选择发布时间', trigger: 'blur' }], releaseStatus: [{ required: true, message: '请选择发布时间', trigger: 'blur' }],
mainTagId: [{ required: true, message: '请选择主标签', trigger: 'blur' }], mainTagId: [{ required: true, message: '请选择主标签', trigger: 'blur' }],
sendType: [{ required: true, message: '请选择发布类型', trigger: 'blur' }], sendType: [{ required: true, message: '请选择发布类型', trigger: 'blur' }],
...@@ -93,12 +92,12 @@ export default defineComponent((_, { expose }) => { ...@@ -93,12 +92,12 @@ export default defineComponent((_, { expose }) => {
type="textarea" type="textarea"
placeholder="分享你的企业文化实践实例" placeholder="分享你的企业文化实践实例"
rows={6} rows={6}
maxlength={1000} maxlength={2000}
show-word-limit show-word-limit
class="content-input" class="content-input"
/> />
</el-form-item> </el-form-item>
<el-form-item label="图片" prop="imgUrl"> <el-form-item label="图片">
{/* @ts-ignore */} {/* @ts-ignore */}
<UploadFile v-model={form.value.imgUrl} /> <UploadFile v-model={form.value.imgUrl} />
</el-form-item> </el-form-item>
......
...@@ -41,10 +41,24 @@ import type { Component } from 'vue' ...@@ -41,10 +41,24 @@ import type { Component } from 'vue'
import { addOrUpdateArticle, addOrUpdatePractice } from '@/api' import { addOrUpdateArticle, addOrUpdatePractice } from '@/api'
import { ArticleTypeEnum, ReleaseStatusTypeEnum } from '@/constants' import { ArticleTypeEnum, ReleaseStatusTypeEnum } from '@/constants'
import LoadingComponent from '@/components/common/LoadingComponent/index.vue' import LoadingComponent from '@/components/common/LoadingComponent/index.vue'
const typeMap: Record<
ArticleTypeEnum, interface ApiMap {
{ title: string; component: Component; api?: (data: any) => Promise<any> } [ArticleTypeEnum.VIDEO]: typeof addOrUpdateArticle
> = { [ArticleTypeEnum.QUESTION]: typeof addOrUpdateArticle
[ArticleTypeEnum.POST]: typeof addOrUpdateArticle
[ArticleTypeEnum.PRACTICE]: typeof addOrUpdatePractice
[ArticleTypeEnum.COLUMN]: typeof addOrUpdateArticle
[ArticleTypeEnum.INTERVIEW]: typeof addOrUpdateArticle
}
interface TypeConfig<T extends ArticleTypeEnum> {
title: string
component: Component
api?: ApiMap[T]
}
const typeMap: {
[key in ArticleTypeEnum]: TypeConfig<key>
} = {
[ArticleTypeEnum.VIDEO]: { [ArticleTypeEnum.VIDEO]: {
title: '视频', title: '视频',
component: defineAsyncComponent({ component: defineAsyncComponent({
...@@ -148,7 +162,7 @@ const handleClosed = () => { ...@@ -148,7 +162,7 @@ const handleClosed = () => {
const handleSubmit = async (releaseStatus: ReleaseStatusTypeEnum) => { const handleSubmit = async (releaseStatus: ReleaseStatusTypeEnum) => {
loading.value = true loading.value = true
try { try {
const formData = await formComponentRef.value?.getValidatedFormData(releaseStatus) const formData = (await formComponentRef.value?.getValidatedFormData(releaseStatus)) as any
if (!formData) return if (!formData) return
console.log(formData) console.log(formData)
await typeMap[articleType.value].api?.({ await typeMap[articleType.value].api?.({
......
...@@ -14,10 +14,13 @@ ...@@ -14,10 +14,13 @@
<div class="flex items-center"> <div class="flex items-center">
<!-- 搜索框 --> <!-- 搜索框 -->
<div class="flex-1 max-w-sm mx-4 hidden md:block lg:max-w-md lg:mx-6"> <div
v-show="showSearchInupt"
class="flex-1 max-w-sm mx-4 hidden md:block lg:max-w-md lg:mx-6"
>
<el-input v-model="search" class="h-8" placeholder="搜索"> <el-input v-model="search" class="h-8" placeholder="搜索">
<template #suffix> <template #suffix>
<el-icon class="text-gray-400" @click="router.push('/searchPage')"> <el-icon class="text-gray-400" @click="router.push('/searchPage?title=' + search)">
<Search /> <Search />
</el-icon> </el-icon>
</template> </template>
...@@ -29,9 +32,10 @@ ...@@ -29,9 +32,10 @@
class="flex items-center cursor-pointer px-2 py-1 rounded transition-colors sm:px-3 sm:py-2 hover:shadow-lg duration-200" class="flex items-center cursor-pointer px-2 py-1 rounded transition-colors sm:px-3 sm:py-2 hover:shadow-lg duration-200"
@click="router.push('/userPage')" @click="router.push('/userPage')"
> >
<!-- 默认展示匿名头像 -->
<img <img
class="w-8 h-8 object-contain flex-shrink-0 rounded-full" class="w-8 h-8 object-contain flex-shrink-0 rounded-full"
:src="userInfo?.avatar" :src="userInfo?.hiddenAvatar"
alt="个人中心" alt="个人中心"
/> />
<span class="ml-2 text-sm text-gray-700 whitespace-nowrap hidden lg:inline" <span class="ml-2 text-sm text-gray-700 whitespace-nowrap hidden lg:inline"
...@@ -71,8 +75,18 @@ ...@@ -71,8 +75,18 @@
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item :command="ArticleTypeEnum.POST">帖子</el-dropdown-item> <el-dropdown-item :command="ArticleTypeEnum.POST">帖子</el-dropdown-item>
<el-dropdown-item :command="ArticleTypeEnum.PRACTICE">实践</el-dropdown-item> <el-dropdown-item :command="ArticleTypeEnum.PRACTICE">实践</el-dropdown-item>
<el-dropdown-item :command="ArticleTypeEnum.COLUMN">专栏</el-dropdown-item> <el-dropdown-item :command="ArticleTypeEnum.VIDEO">视频</el-dropdown-item>
<el-dropdown-item :command="ArticleTypeEnum.INTERVIEW">专访</el-dropdown-item> <el-dropdown-item :command="ArticleTypeEnum.QUESTION">问吧</el-dropdown-item>
<el-dropdown-item
v-if="userInfo.isOfficialAccount || userInfo.isAdmin"
:command="ArticleTypeEnum.COLUMN"
>专栏</el-dropdown-item
>
<el-dropdown-item
v-if="userInfo.isOfficialAccount || userInfo.isAdmin"
:command="ArticleTypeEnum.INTERVIEW"
>专访</el-dropdown-item
>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
...@@ -81,7 +95,7 @@ ...@@ -81,7 +95,7 @@
</div> </div>
<div class="flex-1 w-full flex items-center justify-center"> <div class="flex-1 w-full flex items-center justify-center">
<div <div
class="container max-h-none px-20 lg:px-20 2xl:px-30 transition-all duration-300 min-h-[calc(100vh-96px)]" class="container max-h-none px-0 lg:px-10 2xl:px-43 transition-all duration-300 min-h-[calc(100vh-96px)]"
> >
<router-view v-slot="{ Component, route }"> <router-view v-slot="{ Component, route }">
<transition name="fade" mode="out-in"> <transition name="fade" mode="out-in">
...@@ -94,7 +108,7 @@ ...@@ -94,7 +108,7 @@
</div> </div>
</div> </div>
</div> </div>
<OnlineTime /> <OnlineTime v-if="showOnlineTime" />
<PublishDialog ref="PublishDialogRef" /> <PublishDialog ref="PublishDialogRef" />
</template> </template>
...@@ -112,12 +126,15 @@ const userStore = useUserStore() ...@@ -112,12 +126,15 @@ const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore) const { userInfo } = storeToRefs(userStore)
const router = useRouter() const router = useRouter()
const route = useRoute()
const search = ref('') const search = ref('')
const PublishDialogRef = useTemplateRef<InstanceType<typeof PublishDialog>>('PublishDialogRef') const PublishDialogRef = useTemplateRef<InstanceType<typeof PublishDialog>>('PublishDialogRef')
console.log(route)
const showSearchInupt = computed(() => route.path !== '/searchPage')
// 获取二级路由的 key // 获取二级路由的 key
const getSecondLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => { const getSecondLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => {
// 取第一级作为key homePage // 取第一级作为key homePage
const pathSegments = route.path.split('/').filter(Boolean) const pathSegments = route.path.split('/').filter(Boolean)
// console.log(route) // console.log(route)
// console.log(pathSegments) // console.log(pathSegments)
...@@ -125,18 +142,35 @@ const getSecondLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => { ...@@ -125,18 +142,35 @@ const getSecondLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => {
const key = Object.keys(route.params).length const key = Object.keys(route.params).length
? pathSegments.slice(0, 2).join('/') ? pathSegments.slice(0, 2).join('/')
: pathSegments.slice(0, 1).join('/') : pathSegments.slice(0, 1).join('/')
// console.log(key, '*********************')
return key return key
} }
const handlePost = async (type: string) => { const notShowPath = ['/videoDetail', '/articleDetail', '/questionDetail']
// router.push(command)
PublishDialogRef.value?.open(type) const showOnlineTime = computed(() => {
return notShowPath.every((path) => !route.path.includes(path))
})
const handlePost = async (type: ArticleTypeEnum) => {
if (type === ArticleTypeEnum.VIDEO) {
router.push('/publishVideo')
} else if (type === ArticleTypeEnum.QUESTION) {
// router.push(`/homePage/askTab#tabsRef?t=${Date.now()}`)
router.push(`/publishLongArticle/${type}`)
} else if (type === ArticleTypeEnum.PRACTICE) {
router.push(`/publishLongArticle/${type}`)
// PublishDialogRef.value?.open(type)
} else {
router.push(`/publishLongArticle/${type}`)
}
} }
const isDropdownHover = ref(false) const isDropdownHover = ref(false)
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.layout-culture { .layout-culture {
padding-top: 52px; padding-top: 52px;
.header { .header {
min-height: 52px; min-height: 52px;
color: #212121; color: #212121;
...@@ -145,11 +179,13 @@ const isDropdownHover = ref(false) ...@@ -145,11 +179,13 @@ const isDropdownHover = ref(false)
background-size: 100% 100%; background-size: 100% 100%;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
.container { .container {
.main-container { .main-container {
.tabs-container { .tabs-container {
background: linear-gradient(90deg, #16cdea 0%, #d680ff 100%); background: linear-gradient(90deg, #16cdea 0%, #d680ff 100%);
} }
.right { .right {
.common-box { .common-box {
padding: 15px; padding: 15px;
...@@ -157,6 +193,7 @@ const isDropdownHover = ref(false) ...@@ -157,6 +193,7 @@ const isDropdownHover = ref(false)
border-radius: 10px; border-radius: 10px;
border: 1px solid #efe7dc; border: 1px solid #efe7dc;
} }
.common-btn { .common-btn {
display: flex; display: flex;
align-items: center; align-items: center;
...@@ -178,6 +215,7 @@ const isDropdownHover = ref(false) ...@@ -178,6 +215,7 @@ const isDropdownHover = ref(false)
opacity: 0; opacity: 0;
transform: translateY(10px); transform: translateY(10px);
} }
.fade-enter-active, .fade-enter-active,
.fade-leave-active { .fade-leave-active {
transition: all 0.3s ease; transition: all 0.3s ease;
......
...@@ -10,13 +10,15 @@ import 'virtual:uno.css' ...@@ -10,13 +10,15 @@ import 'virtual:uno.css'
// 注册svg // 注册svg
import 'virtual:svg-icons-register' import 'virtual:svg-icons-register'
import SvgIcon from '@/components/common/SvgIcon/svgIcon.vue' import SvgIcon from '@/components/common/SvgIcon/svgIcon.vue'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
if (import.meta.env.MODE === 'production') {
import('@/utils/version').then(({ loopGetVersion }) => loopGetVersion())
}
const app = createApp(App) const app = createApp(App)
app.use(createPinia()) app.use(createPinia())
app.use(router) app.use(router)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
// 全局组件挂载 // 全局组件挂载
app.component('SvgIcon', SvgIcon) app.component('SvgIcon', SvgIcon)
app.mount('#app') app.mount('#app')
import type { Plugin } from 'vite'
import fs from 'node:fs'
import path from 'node:path'
export default function pushUpdatePlugin(): Plugin {
// const virtualId = 'virtual:push-update'
// const resolveVirtualId = '\0' + virtualId
return {
name: 'push-update-plugin',
apply: 'build',
// resolveId(id) {
// if (id === virtualId) {
// return resolveVirtualId
// }
// },
// load(id) {
// if (id === resolveVirtualId) {
// let updateInfo = ''
// try {
// updateInfo = fs.readFileSync(path.resolve(process.cwd(), 'pushUpdate.txt'), 'utf-8')
// } catch (error) {
// const err = error as NodeJS.ErrnoException
// if (err.code !== 'ENOENT') {
// throw err
// }
// }
// return `export default ${JSON.stringify(updateInfo)}`
// }
// },
writeBundle(options) {
const version = Date.now().toString()
let updateInfo = ''
try {
updateInfo = fs.readFileSync(path.resolve(process.cwd(), 'pushUpdate.txt'), 'utf-8') || ''
} catch (error) {
const err = error as NodeJS.ErrnoException
if (err.code !== 'ENOENT') {
throw err
}
}
const outDir = options.dir || path.resolve(process.cwd(), 'dist')
const filePath = path.resolve(outDir, 'version.json')
fs.writeFileSync(
filePath,
JSON.stringify({ version, updateInfo }, null, 2), // 存成 json
'utf-8',
)
},
}
}
import type { Router } from 'vue-router' import type { Router } from 'vue-router'
import { saveScrollPosition } from './scrollStorage' import { saveScrollPosition } from './scrollStorage'
import { parseCode, parseIsCodeLogin, parseIsCutEmail } from '@/utils/wxUtil' import { parseCode } from '@/utils/wxUtil'
import { useUserStore } from '@/stores' import { useUserStore } from '@/stores'
import { fa } from 'element-plus/es/locales.mjs'
// 白名单 // 白名单
const WHITE_LIST: string[] = ['/aa'] const WHITE_LIST: string[] = ['/aa']
export function registerRouterGuard(router: Router) { export function registerRouterGuards(router: Router) {
router.beforeEach(async (to, from) => { router.beforeEach(async (to, from) => {
// console.log('to', to)
// console.log('from', from)
// 保存当前页面的滚动位置 // 保存当前页面的滚动位置
if (from.fullPath) { if (from.fullPath) {
saveScrollPosition(from.fullPath, window.scrollY) saveScrollPosition(from.fullPath, window.scrollY)
...@@ -18,18 +19,23 @@ export function registerRouterGuard(router: Router) { ...@@ -18,18 +19,23 @@ export function registerRouterGuard(router: Router) {
return true return true
} }
const code = parseCode() const code = parseCode(to.fullPath)
// code是否来自企业微信 1 不是 0 是 2 开发人员登录方式 // code是否来自企业微信 1 不是 0 是 2 开发人员登录方式
const isCodeLogin = parseIsCodeLogin() // const isCodeLogin = parseIsCodeLogin()
const cutEmail = parseIsCutEmail() // const cutEmail = parseIsCutEmail()
console.log(code, isCodeLogin, cutEmail) // console.log(code, isCodeLogin, cutEmail)
const userStore = useUserStore() const userStore = useUserStore()
if (code) { if (code) {
console.log('code', code) // console.log('code', code)
await userStore.getUserInfoByCode(code) await userStore.getUserInfoByCode({
return true code,
isCodeLogin: 0,
})
return {
path: to.path,
replace: true,
}
} else { } else {
return true return true
} }
......
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import layoutCulture from '@/layoutCulture/index.vue'
import { scrollBehavior } from './scrollStorage' import { scrollBehavior } from './scrollStorage'
import { registerRouterGuard } from './regards' import { registerRouterGuards } from './guards'
const routes = [ import { constantsRoute } from './route'
{
path: '/',
name: 'Layout',
component: layoutCulture,
redirect: '/homePage/homeTab',
meta: {
title: '企业文化首页',
},
children: [
{
path: 'homePage',
name: 'CultureHomePage',
// component: () => import('@/layoutCulture/components/mainContainer.vue'),
component: () => import('@/views/homePage/index.vue'),
children: [
{
path: 'homeTab',
name: 'CultureHomeTab',
component: () => import('@/views/homePage/homeTab/index.vue'),
},
{
path: 'yaTab',
name: 'CultureYaTab',
component: () => import('@/views/homePage/yaTab/index.vue'),
},
{
path: 'askTab',
name: 'CultureAskTab',
component: () => import('@/views/homePage/askTab/index.vue'),
},
],
},
{
path: 'videoDetail/:id',
name: 'CultureVideoDetail',
component: () => import('@/views/videoDetail/index.vue'),
},
{
path: 'pointsStore',
name: 'CulturePointsStore',
component: () => import('@/views/pointsStore/index.vue'),
},
{
path: 'articleDetail/:articleId',
name: 'CultureArticleDetail',
component: () => import('@/views/articleDetail/index.vue'),
},
// 发布视频
{
path: 'publishVideo',
name: 'CulturePublishVideo',
component: () => import('@/views/publishVideo/index.vue'),
},
// 个人中心
{
path: 'userPage',
name: 'CultureUserPage',
component: () => import('@/views/userPage/index.vue'),
},
// 去投稿
{
path: 'publishCase',
name: 'CulturePublishCase',
component: () => import('@/views/publishCase/index.vue'),
},
// 搜索页面
{
path: 'searchPage',
name: 'CultureSearchPage',
component: () => import('@/views/searchPage/index.vue'),
},
// 审核
{
path: 'auditArticle/:id',
name: 'CultureAuditArticle',
component: () => import('@/views/auditArticle/index.vue'),
},
// 发布文章
// {
// {
// path: 'deshboard',
// name: 'CultureHome',
// component: () => import('@/views/home/index.vue')
// },
// {
// path: 'ya',
// name: 'CultureYa',
// component: () => import('@/views/ya/index.vue')
// },
// {
// path: 'ask',
// name: 'CultureAsk',
// component: () => import('@/views/ask/index.vue')
// }
],
},
{
path: '/test',
name: 'Test',
component: () => import('@/test.vue'),
},
{
path: '/backend',
name: 'Backend',
component: () => import('@/views/backend/index.vue'),
redirect: '/backend/manager',
children: [
{
path: 'manager',
name: 'ManagerManagement',
component: () => import('@/views/backend/manager/index.vue'),
meta: { title: '企业文化管理员' },
},
{
path: 'tags',
name: 'OfficialManagement',
component: () => import('@/views/backend/tags/index.vue'),
meta: { title: '官方标签' },
},
{
path: 'carousel',
name: 'CarouselManagement',
component: () => import('@/views/backend/carousel/index.vue'),
meta: { title: '轮播图设置' },
},
// {
// path: 'topic-admin',
// name: 'TopicAdminManagement',
// component: () => import('@/views/backend/topic-admin/index.vue'),
// meta: { title: '专栏——管理员' },
// },
{
path: 'columnSettings',
name: 'ColumnSettingsManagement',
component: () => import('@/views/backend/columnSettings/index.vue'),
meta: { title: '专栏——栏目管理' },
},
// {
// path: 'interview-admin',
// name: 'InterviewAdminManagement',
// component: () => import('@/views/backend/interview-admin/index.vue'),
// meta: { title: '专访——管理员' },
// },
{
path: 'interviewSettings',
name: 'InterviewSettingsManagement',
component: () => import('@/views/backend/interviewSettings/index.vue'),
meta: { title: '专访——栏目管理' },
},
{
path: 'videoSettings',
name: 'VideoSettingsManagement',
component: () => import('@/views/backend/videoSettings/index.vue'),
meta: { title: '视频——栏目管理' },
},
{
path: 'videoManage',
name: 'VideoManageManagement',
component: () => import('@/views/backend/videoManage/index.vue'),
meta: { title: '视频管理' },
},
{
path: 'caseManage',
name: 'CaseManageManagement',
component: () => import('@/views/backend/caseManage/index.vue'),
meta: { title: 'YAYA案例库管理' },
},
{
path: 'goodsManage',
name: 'GoodsManageManagement',
component: () => import('@/views/backend/goodsManage/index.vue'),
meta: { title: '积分商城——商品配置' },
},
{
path: 'goodsDistribution',
name: 'GoodsDistributionManagement',
component: () => import('@/views/backend/goodsDistribution/index.vue'),
meta: { title: '积分商城——商品分发' },
},
],
},
]
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
routes, routes: constantsRoute,
// scrollBehavior(to, from, savedPosition) {
// return new Promise((resolve) => {
// // console.log(to, from, savedPosition)
// // 如果有浏览器保存的位置(前进/后退),优先使用
// if (savedPosition) {
// resolve(savedPosition)
// return
// }
// // 如果有锚点,滚动到锚点
// if (to.hash) {
// resolve({ el: to.hash })
// return
// }
// // 检查是否有保存的滚动位置
// const savedScrollY = scrollPositionMap.get(to.fullPath)
// if (savedScrollY !== undefined) {
// resolve({ top: savedScrollY })
// return
// }
// // 默认滚动到顶部
// resolve({ top: 0 })
// })
// },
scrollBehavior, scrollBehavior,
}) })
// 在路由离开前保存当前滚动位置
// router.beforeEach((to, from, next) => { registerRouterGuards(router)
// // 保存当前页面的滚动位置
// if (from.fullPath) {
// scrollPositionMap.set(from.fullPath, window.scrollY)
// }
// next()
// })
registerRouterGuard(router)
export default router export default router
import layoutCulture from '@/layoutCulture/index.vue'
export const constantsRoute = [
{
path: '/',
name: 'Layout',
component: layoutCulture,
redirect: '/homePage/homeTab',
meta: {
title: '企业文化首页',
},
children: [
{
path: 'homePage',
name: 'CultureHomePage',
// component: () => import('@/layoutCulture/components/mainContainer.vue'),
component: () => import('@/views/homePage/index.vue'),
children: [
{
path: 'homeTab',
name: 'CultureHomeTab',
component: () => import('@/views/homePage/homeTab/index.vue'),
},
{
path: 'yaTab',
name: 'CultureYaTab',
component: () => import('@/views/homePage/yaTab/index.vue'),
},
{
path: 'askTab',
name: 'CultureAskTab',
component: () => import('@/views/homePage/askTab/index.vue'),
},
],
},
// 个人中心
{
path: 'userPage',
name: 'CultureUserPage',
redirect: '/userPage/selfPublish',
component: () => import('@/views/userPage/index.vue'),
children: [
// 我的帖子
{
path: 'selfPublish',
name: 'CultureSelfPublish',
component: () => import('@/views/userPage/components/selfPublish.vue'),
},
// 我的草稿
{
path: 'selfDraft',
name: 'CultureSelfDraft',
component: () => import('@/views/userPage/components/selfDraft.vue'),
},
// 我的收藏
{
path: 'selfCollect',
name: 'CultureSelfCollect',
component: () => import('@/views/userPage/components/selfCollect.vue'),
},
// 我的点赞
{
path: 'selfPraise',
name: 'CultureSelfPraise',
component: () => import('@/views/userPage/components/selfPraise.vue'),
},
// 我的案例库
{
path: 'selfCase',
name: 'CultureSelfCase',
component: () => import('@/views/userPage/components/selfCase.vue'),
},
// 我的任务
{
path: 'selfTask',
name: 'CultureSelfTask',
component: () => import('@/views/userPage/components/selfTask.vue'),
},
// 评论回复
{
path: 'selfComment',
name: 'CultureSelfComment',
component: () => import('@/views/userPage/components/selfComment.vue'),
},
// 回答问题
{
path: 'selfAnswer',
name: 'CultureSelfAnswer',
component: () => import('@/views/userPage/components/selfAnswer.vue'),
},
// 审核帖子列表
{
path: 'selfAudit',
name: 'CultureSelfAudit',
component: () => import('@/views/userPage/components/selfAudit.vue'),
},
// 审核举报列表
{
path: 'selfComplaint',
name: 'CultureSelfComplaint',
component: () => import('@/views/userPage/components/selfComplaint.vue'),
},
],
},
// 点进去其他用户的个人中心
{
path: 'otherUserPage/:userId/:isReal',
name: 'CultureOtherUserPage',
component: () => import('@/views/otherUserPage/index.vue'),
},
{
path: 'videoDetail/:id',
name: 'CultureVideoDetail',
component: () => import('@/views/videoDetail/index.vue'),
},
{
path: 'articleDetail/:id',
name: 'CultureArticleDetail',
component: () => import('@/views/articleDetail/index.vue'),
},
{
path: 'pointsStore',
name: 'CulturePointsStore',
component: () => import('@/views/pointsStore/index.vue'),
},
// 发布视频
{
path: 'publishVideo',
name: 'CulturePublishVideo',
component: () => import('@/views/publishVideo/index.vue'),
},
// 去投稿
{
path: 'publishCase',
name: 'CulturePublishCase',
component: () => import('@/views/publishCase/index.vue'),
},
// 搜索页面
{
path: 'searchPage',
name: 'CultureSearchPage',
component: () => import('@/views/searchPage/index.vue'),
},
// 审核
{
path: 'auditArticle/:id',
name: 'CultureAuditArticle',
component: () => import('@/views/auditArticle/index.vue'),
},
// YA文化专栏的搜索
{
path: 'columnSearchList/:id',
name: 'CultureColumnSearchList',
component: () => import('@/views/columnSearchList/index.vue'),
},
// YA文化实践的搜索
{
path: 'practiceSearchList',
name: 'CulturePracticeSearchList',
component: () => import('@/views/practiceSearchList/index.vue'),
},
// YA文化视频的搜索
{
path: 'videoSearchList/:id?',
name: 'CultureVideoSearchList',
component: () => import('@/views/videoSearchList/index.vue'),
},
{
path: 'questionDetail/:id',
name: 'CultureQuestionDetail',
component: () => import('@/views/questionDetail/index.vue'),
},
{
path: 'publishLongArticle/:type',
name: 'CulturePublishLongArticle',
component: () => import('@/views/publishLongArticle/index.vue'),
},
],
},
{
path: '/backend',
name: 'Backend',
component: () => import('@/views/backend/index.vue'),
redirect: '/backend/tags',
children: [
{
path: 'manager',
name: 'ManagerManagement',
component: () => import('@/views/backend/manager/index.vue'),
meta: { title: '企业文化管理员' },
},
{
path: 'tags',
name: 'OfficialManagement',
component: () => import('@/views/backend/tags/index.vue'),
meta: { title: '官方标签' },
},
{
path: 'carousel',
name: 'CarouselManagement',
component: () => import('@/views/backend/carousel/index.vue'),
meta: { title: '轮播图设置' },
},
// {
// path: 'topic-admin',
// name: 'TopicAdminManagement',
// component: () => import('@/views/backend/topic-admin/index.vue'),
// meta: { title: '专栏——管理员' },
// },
{
path: 'columnSettings',
name: 'ColumnSettingsManagement',
component: () => import('@/views/backend/columnSettings/index.vue'),
meta: { title: '专栏——栏目管理' },
},
// {
// path: 'interview-admin',
// name: 'InterviewAdminManagement',
// component: () => import('@/views/backend/interview-admin/index.vue'),
// meta: { title: '专访——管理员' },
// },
{
path: 'interviewSettings',
name: 'InterviewSettingsManagement',
component: () => import('@/views/backend/interviewSettings/index.vue'),
meta: { title: '专访——栏目管理' },
},
{
path: 'videoSettings',
name: 'VideoSettingsManagement',
component: () => import('@/views/backend/videoSettings/index.vue'),
meta: { title: '视频——栏目管理' },
},
{
path: 'videoManage',
name: 'VideoManageManagement',
component: () => import('@/views/backend/videoManage/index.vue'),
meta: { title: '视频管理' },
},
{
path: 'caseManage',
name: 'CaseManageManagement',
component: () => import('@/views/backend/caseManage/index.vue'),
meta: { title: 'YAYA案例库管理' },
},
{
path: 'goodsManage',
name: 'GoodsManageManagement',
component: () => import('@/views/backend/goodsManage/index.vue'),
meta: { title: '积分商城——商品配置' },
},
{
path: 'goodsDistribution',
name: 'GoodsDistributionManagement',
component: () => import('@/views/backend/goodsDistribution/index.vue'),
meta: { title: '积分商城——商品分发' },
},
],
},
]
...@@ -37,28 +37,33 @@ export function clearScrollPosition(path?: string): void { ...@@ -37,28 +37,33 @@ export function clearScrollPosition(path?: string): void {
*/ */
export const scrollBehavior: RouterScrollBehavior = (to, from, savedPosition) => { export const scrollBehavior: RouterScrollBehavior = (to, from, savedPosition) => {
return new Promise((resolve) => { return new Promise((resolve) => {
console.log('触发路由滚动')
// 1. 如果有浏览器保存的位置(前进/后退),优先使用 // 1. 如果有浏览器保存的位置(前进/后退),优先使用
if (savedPosition) { if (savedPosition) {
resolve(savedPosition) resolve(savedPosition)
return return
} }
// 2. 如果有锚点,滚动到锚点 // 2. 如果有锚点, 约定 默认不滚动 然后在具体的组件里面onActivated里面 或者watch处理滚动逻辑 以及漫游逻辑等
if (to.hash) { if (to.hash) {
resolve({ // console.log(to.hash, window.scrollY)
el: to.hash, // resolve({
behavior: 'smooth', // 平滑滚动 // top: window.scrollY,
}) // })
return return
} }
// 3. 检查是否有保存的滚动位置 // 3. 检查是否有保存的滚动位置
const savedScrollY = scrollPositionMap.get(to.fullPath) const savedScrollY = scrollPositionMap.get(to.fullPath)
if (savedScrollY !== undefined) { if (savedScrollY !== undefined) {
resolve({ setTimeout(() => {
top: savedScrollY, resolve({
behavior: 'smooth', top: savedScrollY,
}) behavior: 'smooth',
})
}, 300)
return return
} }
......
import { createPinia } from 'pinia'
import type { App } from 'vue'
const store = createPinia()
export default store
export function setupStore(app: App) {
app.use(store)
}
export * from './modules/user'
export * from './modules/device'
export * from './modules/app'
export * from './modules/post'
export * from './modules/emoji'
import { localCache } from '@/utils/storage'
import { defineStore } from 'pinia'
import type { RouteLocationNormalized } from 'vue-router'
type ICallbackType = (...args: any[]) => any
export const useAppStore = defineStore('app', {
id: 'app',
state: () => {
return {
// 上一级路由
lastRoute: {} as RouteLocationNormalized,
// 移动端下拉刷新 0不显示 1下拉中 2准备刷新 3 刷新中 4 刷新完成
refreshStatus: 0,
// 移动端是否弹出键盘
isShowKeyBoard: false,
// 记忆页面滚动位置,key:页面地址 value:Y轴滚动
cacheScrollPage: {} as { [key: string]: number },
// 应用内是否存在全局覆盖层,如果存在,返回上一级改成退出全局覆盖层
// 如:图片预览,评论弹窗
fullScreenInstance: {
state: 0,
instance: null, // 打开的示例
close: undefined as ICallbackType | undefined, // 关闭方法
curUrl: '', // 页面Url
},
// 上一次公告弹出时间
announcementTime: 0,
}
},
actions: {
updateLastRoute(route: RouteLocationNormalized) {
this.lastRoute = route
},
updateRefreshStatus(status: number) {
this.refreshStatus = status
},
//软键盘收起的事件处理
handleShowKeyBoard(device = '') {
// ios处理
if (device === 'ios') {
document.body.addEventListener('focusin', () => {
this.isShowKeyBoard = true
})
document.body.addEventListener('focusout', () => {
this.isShowKeyBoard = false
})
} else if (device === 'android') {
const originalHeight = document.documentElement.clientHeight || document.body.clientHeight
window.onresize = () => {
//键盘弹起与隐藏都会引起窗口的高度发生变化
const resizeHeight = document.documentElement.clientHeight || document.body.clientHeight
if (resizeHeight - 0 < originalHeight - 0) {
this.isShowKeyBoard = true
} else {
this.isShowKeyBoard = false
}
}
}
},
/**
* 页面滚动位置
*/
setScrollPage(url: string, top: number) {
this.cacheScrollPage[url] = top
},
getScrollPage(url: string) {
return this.cacheScrollPage[url]
},
/**
* 全局覆盖层
* @returns 返回false,说明 不要返回上一页,而是关闭全局覆盖层
*/
closeFullScreen() {
if (this.fullScreenInstance.state) {
this.fullScreenInstance.close && this.fullScreenInstance.close()
this.clearFullScreenInstance()
return false
}
return true
},
// 清除全屏模态框实例
clearFullScreenInstance() {
this.fullScreenInstance = {
state: 0,
curUrl: '',
instance: null,
close: undefined,
}
},
// 插入全屏模态框示例
updateFullScreenInstance(curUrl: string, instance: any, closeCallback: any) {
this.fullScreenInstance = {
state: 1,
instance,
curUrl,
close: closeCallback,
}
},
/**
* 公告时间
*/
showAnnouncement() {
localCache.setCache('announcementTime', Date.now())
},
getAnnouncement() {
this.announcementTime = localCache.getCache<number>('announcementTime') || 0
return this.announcementTime
},
},
})
import { checkUserAgent } from '@/utils/app'
import { defineStore } from 'pinia'
export const useDeviceStore = defineStore({
id: 'device',
state: () => {
return {
device: '',
isPc: false,
AndroidOrIphone: ''
}
},
actions: {
updateDevice(device: string) {
this.device = device
console.log(['sm', 'md'].indexOf(device) === -1)
// this.updateDeviceType(['sm','md'].indexOf(device) === -1)
},
updateDeviceType(isPc: boolean) {
this.isPc = isPc
},
// 判断设备
handleUserAgent() {
this.AndroidOrIphone = checkUserAgent()
}
}
})
/**
* 表情相关的 缓存
*/
import { defineStore } from 'pinia'
export interface IEmoji {
url: string
name: string
group: string
className: string
}
interface IEmojiObj {
[name: string]: IEmoji
}
interface IGroupEmojy {
[groupName: string]: IEmojiObj
}
export const useEmojiStore = defineStore({
id: 'emoji',
state: () => {
return {
emoji: {} as IGroupEmojy
}
},
actions: {
/**
* 获取emojy标签
*/
async getEmojiUrl(name: string, group: string) {
if (this.emoji[group]) {
return this.emoji[group][name]
}
const data = await this.generateEmojiGroup(group)
if (!data) {
return
}
this.emoji[group] = data
return this.emoji[group][name]
},
async generateEmojiGroup(group: string): Promise<IEmojiObj | null> {
let jsonData
// 这里必须这样写,否则vite打包,会导致这个找不到文件
if (group === 'face') {
jsonData = (await import(`../../utils/emoji/face.json`)) as { default: IEmoji[] }
}
if (!jsonData) return null
const obj: IEmojiObj = {}
jsonData.default.forEach(v => {
obj[v.name] = v
})
return obj
}
}
})
/**
* 帖子相关的 缓存
*/
import {
type IOfficialTagList,
type IPostType,
type IShieldType,
getColleagueTagList,
getPostType,
getShieldTypeList,
} from '@/api'
import { localCache } from '@/utils/storage'
import { defineStore } from 'pinia'
interface ICachePostTime {
[id: string]: number
}
export const usePostStore = defineStore({
id: 'post',
state: () => {
return {
// 缓存上一次查看帖子的时间
cachePostTime: {} as { [id: string]: number },
// 分类数据
typeData: [] as IPostType[],
// 已屏蔽的分类
shieldTypeData: [] as IShieldType[],
// 官方标签
tagData: [] as IOfficialTagList[],
}
},
actions: {
/**
* 页面滚动位置
*/
setPostTime(id: string, number: number) {
this.cachePostTime[id] = number
localCache.setCache('post_time', this.cachePostTime, 1000 * 60 * 60 * 48)
},
getPostTime(id: string) {
if (this.cachePostTime[id]) {
return this.cachePostTime[id]
}
const time = localCache.getCache<ICachePostTime>('post_time')
if (time) {
return time[id]
}
return undefined
},
/**
* 分类获取
*/
async queryPostType() {
if (this.typeData.length > 0) {
return this.typeData
}
const res = await getPostType()
this.typeData = res.data
return this.typeData
},
/**
* 获取屏蔽帖子分类列表
* @param isFresh 是否强制刷新
*/
async queryShieType(isFresh = false) {
if (!isFresh && this.shieldTypeData.length > 0) {
return this.shieldTypeData
}
const res = await getShieldTypeList()
this.shieldTypeData = res.data
return this.shieldTypeData
},
/**
* 标签获取
*/
async queryOfficialTag() {
if (this.tagData.length > 0) {
return this.tagData
}
const res = await getColleagueTagList()
this.tagData = res.data
return this.tagData
},
},
})
import { defineStore } from 'pinia'
import { localCache } from '@/utils/storage'
import {
getChatPeople,
getPendingReviewCount,
getUserByCode,
type IPendingCountMap,
type IUserInfo,
} from '@/api'
export const useUserStore = defineStore({
id: 'user',
state: () => {
return {
id: '',
name: '',
isManager: false,
isOfficial: false, // 是否官方
userInfo: {} as IUserInfo,
expireTime: 0, // 过期时间
// 私信:消息维度
unReadNotice: 0,
// 管理员未处理数量
unDoneNum: {} as IPendingCountMap,
// 是否已被封禁
isBanned: false,
// 没有权限名单
// noAuthList: ['yuxueqin']
noAuthList: [] as string[],
}
},
getters: {
getUserInfo(state) {
if (state.userInfo && Object.keys(state.userInfo).length) return state.userInfo
const userInfo = localCache.getCache<IUserInfo>('userInfo')
if (userInfo) {
state.userInfo = userInfo
state.id = userInfo.userId
state.name = userInfo.username
state.isManager = userInfo.hasManagerBtn
state.isBanned = !!userInfo.isBan
state.isOfficial = !!userInfo.isOfficialAccount
}
return userInfo
},
getExpireTime(state) {
if (state.expireTime) return state.expireTime
const expireTime = localCache.getCache<number>('expireTime')
if (expireTime) {
state.expireTime = expireTime
}
return expireTime
},
isNoAuth(state) {
return state.noAuthList.includes(state.userInfo.account)
},
manageArea(state) {
return (state.userInfo.manageArea || '').split(',') || []
},
},
actions: {
// 判断是否拥有管理权限,传入地区,不传则不判断地区
hasManagerPermission(area?: string) {
if (!this.isManager) return false
if (area) {
return this.manageArea.includes(area)
}
return true
},
setUserInfo(userInfo: IUserInfo) {
const expireTime = Date.now() + 3600 * 1000 * 24 * 4 // 4个小时
this.id = userInfo.userId
this.name = userInfo.username
this.isManager = userInfo.hasManagerBtn
this.isOfficial = !!userInfo.isOfficialAccount
this.isBanned = !!userInfo.isBan
this.userInfo = userInfo
this.expireTime = expireTime
localCache.setCache('userInfo', userInfo)
localCache.setCache('expireTime', expireTime)
},
/**
* 通过code获取用户数据
* @param isCodeLogin code来自系统生成还是企业微信 0 来自企业微信,1来自系统生成,默认来自企业微信
*/
async getUserInfoByCode(code: string, isCodeLogin: number = 0, cutEmail?: string) {
try {
const res = await getUserByCode(code, isCodeLogin, cutEmail)
this.setUserInfo(res.data)
} catch (error) {
return Promise.reject(error)
}
},
// 获取未读消息
async refreshNoticeStatus() {
const res = await getChatPeople()
this.unReadNotice = res.data.dialogList.reduce((pre, cur) => {
return cur.un_read_count + pre
}, 0)
},
// 获取管理员未处理数量
async getPendingReviewCount() {
if (!this.isManager) return
const res = await getPendingReviewCount()
const obj: IPendingCountMap = {}
res.data.forEach((v) => {
if (v.type === 1) {
obj.auditCount = v
} else if (v.type === 2) {
obj.topCount = v
} else if (v.type === 3) {
obj.reportCount = v
} else if (v.type === 4) {
obj.unbanCount = v
}
})
this.unDoneNum = obj
},
},
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment