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
# Vitest
__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'
import archiver from 'archiver'
import fs from 'node:fs'
import path from 'node:path'
import inquirer from 'inquirer'
const unzipDirMode = {
spawn: 'npm run build:prod',
......@@ -33,6 +34,13 @@ let conn = new ssh.Client()
async function start() {
try {
if (process.argv.slice(2).includes('--update-info')) {
// 0. 获取更新信息
const updateInfo = await getUpdateInfo()
if (updateInfo.length) {
writeUpdateInfo(updateInfo)
}
}
// 1. 打包
await build()
// 2. 压缩zip
......@@ -49,11 +57,44 @@ async function start() {
// 5. 断开ssh,并删除本地压缩包
conn.end()
delZip()
delUpdateInfo()
}
}
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. 本地构建项目
*/
function build() {
......@@ -87,11 +128,13 @@ function startZip() {
gzip: true, // 如果需要压缩,可以使用 gzip
gzipOptions: { level: 9 }, // gzip 压缩级别
}).on('error', (err) => reject(err))
console.log('zipPath', zipPath)
// console.log('zipPath', zipPath)
const output = fs.createWriteStream(zipPath)
//监听流的打包
output.on('close', (err) => {
console.log('err', err)
if (err) {
return console.error('打包失败', err)
}
console.log('目标打包完成')
resolve(true)
})
......
/// <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 @@
"lint": "eslint . --fix --cache",
"format": "prettier --write src/",
"build-only": "vite build",
"build:dev": "nvm use 20 && vite build --mode development",
"build:test": "nvm use 20 && vite build --mode test",
"deploy:test": "node deploy/deploytest.js",
"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": {
"@element-plus/icons-vue": "^2.3.2",
"@vueuse/components": "^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",
"axios": "^1.13.0",
"dayjs": "^1.11.19",
"element-plus": "^2.11.5",
"inquirer": "^13.0.2",
"pinia": "^3.0.3",
"ssh2": "^1.17.0",
"vue": "^3.5.22",
"vue-router": "^4.6.3"
},
"devDependencies": {
"@iconify-json/ep": "^1.2.3",
"@tsconfig/node22": "^22.0.2",
"@types/node": "^22.18.11",
"@vitejs/plugin-vue": "^6.0.1",
......@@ -40,15 +47,18 @@
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.6.0",
"@vue/tsconfig": "^0.8.1",
"baseline-browser-mapping": "^2.9.14",
"eslint": "^9.37.0",
"eslint-plugin-vue": "~10.5.0",
"jiti": "^2.6.1",
"npm-run-all2": "^8.0.4",
"prettier": "3.6.2",
"rollup-plugin-visualizer": "^6.0.5",
"sass-embedded": "^1.93.2",
"typescript": "~5.9.0",
"unocss": "^66.5.4",
"unplugin-auto-import": "^20.2.0",
"unplugin-icons": "^22.5.0",
"unplugin-vue-components": "^30.0.0",
"vite": "^7.1.11",
"vite-plugin-svg-icons": "^2.0.1",
......
......@@ -7,10 +7,16 @@
</template>
<script setup lang="ts">
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 userStore = useUserStore()
// userStore.fetchUserInfo().then((res) => {
// console.log(res)
// })
onMounted(() => {
console.table(__CORE_LIB_VERSION__)
if (import.meta.env.MODE === 'production') {
setTimeout(() => {
initWxConfig()
}, 3000)
}
})
</script>
......@@ -10,6 +10,13 @@ import type {
CommentSearchParams,
InterviewItemDto,
ColumnItemDto,
VideoOptionDto,
CommentChildrenSearchParams,
SearchMoreColumnParams,
SearchMoreColumnItemDto,
SearchMoreVideoParams,
SearchMoreVideoItemDto,
SecondCommentItemDto,
} from './types'
import type { BackendServicePageResult, PageSearchParams } from '@/utils/request/types'
......@@ -82,6 +89,16 @@ export const getColumnOptions = () => {
}
/**
* 获取视频栏目列表 --不分页 用户新增的时候传
*/
export const getVideoOptions = () => {
return service.request<VideoOptionDto[]>({
url: '/api/cultureColumn/listNoPage?type=video',
method: 'POST',
})
}
/**
* 获取首页专栏列表list —— 分页
*/
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 —— 分页
*/
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) => {
......@@ -206,3 +249,108 @@ export const addOrCancelArticleReward = (data: { articleId: number; ayabi: numbe
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 {
type?: ArticleTypeEnum
sortLogic?: number
title?: string
questionFocus?: number
}
/**
......@@ -61,7 +62,7 @@ export interface AddOrUpdateColumnForm extends AddOrUpdateColumnBase {
/**
* 添加专栏的DTO
*/
export interface AddOrUpdateColumnDto extends AddOrUpdateColumnBase {
export interface AddOrUpdateColumnDto extends AddOrUpdateColumnBase, PushSettingBase {
tagList: { tagId: number; sort: number }[]
}
......@@ -85,10 +86,16 @@ export interface AddOrUpdateInterviewForm extends AddOrUpdateInterviewBase {
tagList: number[]
}
interface PushSettingBase {
pushType: SendTypeEnum
pushTime: string
pushList: { valueId: string; valueName: string | number }[]
}
// 推送设置相关的字段
/**
* 添加专访的DTO
*/
export interface AddOrUpdateInterviewDto extends AddOrUpdateInterviewBase {
export interface AddOrUpdateInterviewDto extends AddOrUpdateInterviewBase, PushSettingBase {
tagList: { tagId: number; sort: number }[]
}
......@@ -107,6 +114,7 @@ export interface AddOrUpdateVideoDto {
isRecommend?: BooleanFlag
mainTagId: string | number
tagList: { tagId: number; sort: number }[] | number[]
relateColumnId?: number
}
/**
......@@ -124,9 +132,10 @@ export interface ArticleItemDto {
faceUrl: string
videoUrl: string
description: string
createUserId: number
createUserId: string
createTime: number
viewCount: number
playCount: number
isRecommend: BooleanFlag
type: ArticleTypeEnum
isRelateColleague: BooleanFlag
......@@ -144,6 +153,46 @@ export interface ArticleItemDto {
showName: string
videoDuration: string
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 {
title: string
color: string
sort: number
id: number
yaColumnVoList: {
articleId: number
collectCount: number
......@@ -182,6 +232,10 @@ export interface ColumnItemDto {
title: string
type: ArticleTypeEnum.COLUMN
viewCount: number
videoDuration: string
showName: string
showAvatar: string
playCount: number
}[]
}
......@@ -199,6 +253,21 @@ export interface InterviewOptionDto {
title: string
type: 'column'
}
/**
* 视频选项
*/
export interface VideoOptionDto {
color: string
createTime: number
createUserId: number
id: number
isDelete: BooleanFlag
sort: number
status: BooleanFlag
title: string
type: 'video'
}
/**
* 专访列表Item
......@@ -223,6 +292,10 @@ export interface InterviewItemDto {
viewCount: number
}[]
}
/**
* 视频选项
*/
/**
* 评论列表
*/
......@@ -230,6 +303,13 @@ export interface CommentSearchParams extends PageSearchParams {
articleId: number | string
sortType: number
}
/**
* 获取子评论列表
*/
export interface CommentChildrenSearchParams extends PageSearchParams {
pid: number | string
articleId: number | string
}
/**
* 新增评论
......@@ -237,7 +317,7 @@ export interface CommentSearchParams extends PageSearchParams {
export interface AddCommentDto {
articleId: number | string
content: string
pId?: number | string
pid?: number | string
}
/**
......@@ -261,5 +341,85 @@ export interface CommentItemDto {
regionHide: number
replyUser: 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 type { AddOrUpdateTagDto, BackendTagListItemDto, BackendTagSearchParams } from './types'
import type {
ArticleSearchParams,
AddOrUpdateCarouselDto,
BackendArticleListItemDto,
BackendCarouselListItemDto,
} from './types'
import type { BackendServicePageResult } from '@/utils/request/types'
// 后台管理标签相关接口
......@@ -7,7 +12,7 @@ import type { BackendServicePageResult } from '@/utils/request/types'
* 获取轮播图列表 不分页 数量不多
*/
export const getCarouselList = () => {
return service.request<BackendServicePageResult<BackendTagListItemDto>>({
return service.request<BackendCarouselListItemDto[]>({
url: '/api/cultureCarousel/listNoPage',
method: 'POST',
data: {},
......@@ -15,7 +20,7 @@ export const getCarouselList = () => {
}
/**
* 添加轮播
* 添加轮播
*/
export const addCarousel = (data: AddOrUpdateCarouselDto) => {
return service.request({
......@@ -37,11 +42,12 @@ export const editCarousel = (data: AddOrUpdateCarouselDto) => {
}
/**
* 更改发布状态
* 根据类型获取文章列表
*/
export const deleteCarousel = (id: number) => {
return service.request<boolean>({
url: `/api/cultureCarousel/updateRelease?id=${id}`,
export const getArticleList = (data: ArticleSearchParams) => {
return service.request<BackendServicePageResult<BackendArticleListItemDto>>({
url: `/api/cultureArticle/listByPage`,
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 type { AddOrUpdateTagDto, BackendTagListItemDto, BackendTagSearchParams } from './types'
import type {
ChangeUsageStatusDto,
AuditCaseDto,
BackendCaseListItemDto,
CaseListSearchParams,
} from './types'
import type { BackendServicePageResult } from '@/utils/request/types'
// 后台管理案例库相关接口
/**
* 案例库后台列表
*/
export const getCaseList = (data: PageSearchParams) => {
return service.request<BackendServicePageResult<BackendTagListItemDto>>({
export const getCaseList = (data: CaseListSearchParams) => {
return service.request<BackendServicePageResult<BackendCaseListItemDto>>({
url: '/api/cultureCase/caseListByPage',
method: 'POST',
data,
......@@ -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({
url: '/api/cultureCase/auditCase',
method: 'POST',
......@@ -26,11 +41,13 @@ export const auditCase = (data: AddOrUpdateCarouselDto) => {
}
/**
* 删除案例库
* 修改使用状态
*/
export const deleteCase = (id: number) => {
export const changeUsageStatus = (data: ChangeUsageStatusDto) => {
return service.request({
url: `/api/cultureCase/deleteCase?id=${id}`,
url: '/api/cultureCase/userCase',
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 {
color: string
createTime: number
createUserId: number
content: string
id: number
isDelete: number
postCount: number
......
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'
// 后台管理 积分商城相关接口
/**
* 商品配置列表
*/
export const getShopItemList = (params: BackendTagSearchParams) => {
return service.request<BackendServicePageResult<BackendTagListItemDto>>({
export const getShopItemList = (params: BackendShopListSearchParams) => {
return service.request<BackendServicePageResult<BackendShopItemDto>>({
url: '/api/culture/shop/item/productList',
method: 'POST',
data: params,
......@@ -17,8 +22,8 @@ export const getShopItemList = (params: BackendTagSearchParams) => {
/**
* 新增/编辑商品
*/
export const addOrUpdateShopItem = (data: AddOrUpdateTagDto) => {
return service.request<BackendServicePageResult<BackendTagListItemDto>>({
export const addOrUpdateShopItem = (data: AddOrUpdateShopItemDto) => {
return service.request({
url: '/api/culture/shop/item/addOrUpdate',
method: 'POST',
data,
......@@ -37,8 +42,8 @@ export const deleteShopItem = (id: number) => {
/**
* 后台商品领用列表
*/
export const getBackendExchangeList = (data: BackendTagSearchParams) => {
return service.request<BackendServicePageResult<BackendTagListItemDto>>({
export const getBackendExchangeList = (data: BackendExchangeListSearchParams) => {
return service.request<BackendServicePageResult<BackendShopItemDto>>({
url: '/api/culture/shop/order/background/productList',
method: 'POST',
data,
......@@ -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({
url: `/api/culture/shop/order/issueProduct`,
method: 'POST',
......
import type { PageSearchParams } from '@/utils/request/types'
export interface BackendTagSearchParams extends PageSearchParams {
title: string
type: string
import { BooleanFlag, ShopGoodsTypeEnum } from '@/constants'
export interface BackendShopListSearchParams extends PageSearchParams {
name?: string
itemType?: ShopGoodsTypeEnum
region?: string
enable?: 0 | 1
}
export interface AddOrUpdateTagDto {
export interface BackendShopItemDto {
id?: number
color: string
description: string
sort: number
enable: BooleanFlag
imageUrl: string
itemType: ShopGoodsTypeEnum
name: string
price: number
region: string
title: string
type: string
description: string
imgUrl: string
stock: number
status: number
}
export interface BackendTagListItemDto {
color: string
createId: number
createTime: number
export interface AddOrUpdateShopItemDto {
id?: number
title: string
description: string
id: number
imgUrl: string
style: number
title: string
type: string
price: number
stock: number
status: number
}
export interface BackendExchangeListSearchParams extends PageSearchParams {
source: string
status?: 0 | 1 | 2
itemName?: string
}
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) => {
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 @@
*/
import { TagTypeEnum, TagLevelEnum, BooleanFlag, ReleaseStatusTypeEnum } from '@/constants'
export type TagItemDto = {
type TagItemDto = {
tagId: number
type: TagTypeEnum
keywordType: TagLevelEnum
}
export interface AddOrUpdateCaseDto {
id?: number
content: string
title: string
tagRelationDtoList: TagItemDto[]
deptId: string
deptName: string
isSync: BooleanFlag
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 type { FielItemDto } from './types'
// import service from '@/utils/request/index'
// import type { FielItemDto } from './types'
/**
* 获取常规的接口
*/
......@@ -21,14 +21,55 @@ import type { FielItemDto } from './types'
/**
* 暂时调用oa正式接口
*/
import axios from 'axios'
export const uploadFile = (file: File, onProgress?: (progress: number) => void) => {
import axios, { type AxiosRequestConfig } from 'axios'
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()
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))
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'
export * from './sign'
export * from './article'
export * from './shop'
// export * from './column'
// export * from './interview'
export * from './tag'
export * from './article'
export * from './user'
......@@ -14,12 +12,12 @@ export * from './practice'
export * from './common'
export * from './login'
export * from './article'
export * from './online'
export * from './otherUserPage'
// 导出类型
export * from './task/types'
export * from './shop/types'
export * from './article/types'
// export * from './column/types'
// export * from './interview/types'
export * from './tag/types'
export * from './article/types'
export * from './user/types'
......@@ -29,3 +27,5 @@ export * from './practice/types'
export * from './common/types'
export * from './login/types'
export * from './article/types'
export * from './online/types'
export * from './otherUserPage/types'
// 专栏列表
import service from '@/utils/request/index'
import type { ColumnOptionDto } from './types'
import type { InterviewOptionDto } from './types'
/**
* 获取专栏列表
*/
export const getInterviewOptions = () => {
return service.request<ColumnOptionDto[]>({
return service.request<InterviewOptionDto[]>({
url: '/api/cultureColumn/listNoPage?type=interview',
method: 'POST',
})
......
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 = ({
* 生成随机密钥-切换官方账号用
*/
interface GenerateLoginKeyData {
cutEmail: string
cutEmail?: string
timestamp: number
type: 1 | 2 // 1: 多平台跳转 2: 官方账号切换
userId: number
type: 1 | 2 // 1: 多平台跳转 清除缓存 2: 官方账号切换
userId: string
}
export const generateLoginKey = (data: GenerateLoginKeyData) => {
return service.request<string>({
......@@ -53,3 +54,18 @@ export const generateLoginKey = (data: GenerateLoginKeyData) => {
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 {
permissions: any[] | null
userRelated: any[] | null
isOfficialAccount: boolean | null
isAdmin: boolean
firstDeptId: number | null
password: string | null
enabled: boolean
......@@ -25,4 +26,16 @@ export interface LoginResponseDto {
credentialsNonExpired: boolean
accountNonLocked: boolean
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 {
* 搜索文章的参数
*/
export interface PracticeSearchParams extends PageSearchParams {
sortLogic?: number
tagIdList?: number[]
}
/**
* 添加或更新文章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 }[]
/** 标题 */
sortLogic: number
tagIdList: number[]
deptIdList: 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 type { BackendServicePageResult, PageSearchParams } from '@/utils/request/types'
import type { BackendServicePageResult } from '@/utils/request/types'
import type {
ExchangeGoodsParams,
ExchangeGoodsRecordItemDto,
ShopItemDto,
ShopSearchParams,
YaBiData,
ExchangeYabiRecordItemDto,
ExchangeGoodsRecordSearchParams,
BackendShopItemDto,
ExchangeYabiRecordSearchParams,
} from './types'
/**
* 获取用户亚币相关数据
......@@ -57,7 +57,7 @@ export const getExchangeGoodsRecordList = (data: ExchangeGoodsRecordSearchParams
/**
* 获取用户YA币兑换记录
*/
export const getExchangeYabiRecordList = (data: PageSearchParams) => {
export const getExchangeYabiRecordList = (data: ExchangeYabiRecordSearchParams) => {
return service.request<BackendServicePageResult<ExchangeYabiRecordItemDto>>({
url: '/api/culture/action/record/yabiList',
method: 'POST',
......
......@@ -91,3 +91,11 @@ export interface ExchangeYabiRecordItemDto {
subType: string
userId: number
}
/**
* 获取用户YA币兑换记录搜索参数
*/
export interface ExchangeYabiRecordSearchParams extends PageSearchParams {
type: ShopGoodsTypeEnum
dateRange: [number, number]
}
......@@ -4,12 +4,12 @@ import type { TagItemDto } from './types'
/**
* 获取标签 不分页
*/
export const getTagList = () => {
export const getTagList = (type?: string) => {
return service.request<TagItemDto[]>({
url: '/api/cultureTag/listNoPage',
method: 'POST',
data: {
type: 'culture',
type,
},
})
}
......@@ -10,8 +10,19 @@ import type {
SelfCollectDetailDto,
SelfPraiseSearchParams,
SelfPraiseDetailDto,
OfficialAccountItemDto,
SelfCaseItemDto,
SelfCommentItemDto,
SelfCommentSearchParams,
SelfDraftSearchParams,
SelfTaskSearchParams,
SelfTaskItemDto,
ComplaintListItemDto,
AuditComplaintDto,
ComplaintListSearchParams,
SelfCaseSearchParams,
} 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) => {
* 是否有官方账号权限
*/
export const hasOfficialAccount = () => {
return service.request<[]>({
return service.request<OfficialAccountItemDto[]>({
url: '/api/personalCenter/getIsOfficial',
method: 'POST',
data: {},
......@@ -49,7 +60,7 @@ export const getSelfPublishList = (data: SelfPublishSearchParams) => {
/**
* 获取我的草稿列表
*/
export const getSelfDraftList = (data: PageSearchParams) => {
export const getSelfDraftList = (data: SelfDraftSearchParams) => {
return service.request<BackendServicePageResult<SelfPublishDetailDto>>({
url: '/api/personalCenter/selfDraft',
method: 'POST',
......@@ -82,16 +93,8 @@ export const getSelfPraiseList = (data: SelfPraiseSearchParams) => {
/**
* 获取我的案例库列表
*/
export const getSelfCaseList = (data: PageSearchParams) => {
return service.request<
BackendServicePageResult<{
id: number
title: string
content: string
createTime: string
updateTime: string
}>
>({
export const getSelfCaseList = (data: SelfCaseSearchParams) => {
return service.request<BackendServicePageResult<SelfCaseItemDto>>({
url: '/api/personalCenter/selfCase',
method: 'POST',
data,
......@@ -101,8 +104,8 @@ export const getSelfCaseList = (data: PageSearchParams) => {
/**
* 获取我的任务列表
*/
export const getSelfTaskList = (data: PageSearchParams) => {
return service.request<BackendServicePageResult<SelfPublishDetailDto>>({
export const getSelfTaskList = (data: SelfTaskSearchParams) => {
return service.request<BackendServicePageResult<SelfTaskItemDto>>({
url: '/api/personalCenter/selfTaskConfig',
method: 'POST',
data,
......@@ -112,8 +115,8 @@ export const getSelfTaskList = (data: PageSearchParams) => {
/**
* 评论回复
*/
export const getSelfCommentList = (data: PageSearchParams) => {
return service.request({
export const getSelfCommentList = (data: SelfCommentSearchParams) => {
return service.request<BackendServicePageResult<SelfCommentItemDto>>({
url: '/api/personalCenter/selfComment',
method: 'POST',
data,
......@@ -141,3 +144,25 @@ export const auditArticle = (data: AuditArticleDto) => {
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'
export interface SelfPublishSearchParams extends PageSearchParams {
......@@ -21,7 +29,7 @@ export interface SelfPublishDetailDto {
replyCount: number
tagNameList: string[]
title: string
type: string
type: ArticleTypeEnum
videoUrl: string
viewCount: number
}
......@@ -45,7 +53,7 @@ export interface SelfCollectDetailDto {
replyCount: number
tagNameList: string[]
title: string
type: string
type: ArticleTypeEnum
viewCount: number
}
......@@ -54,6 +62,11 @@ export interface SelfPraiseSearchParams extends PageSearchParams {
type: ArticleTypeEnum
}
// 我的案例库搜索
export interface SelfCaseSearchParams extends PageSearchParams {
releaseStatus: ReleaseStatusTypeEnum
}
export interface SelfPraiseDetailDto {
collectionCount: number
content: string
......@@ -70,11 +83,16 @@ export interface SelfPraiseDetailDto {
replyCount: number
tagNameList: string[]
title: string
type: string
type: ArticleTypeEnum
videoUrl: string
viewCount: number
}
//草稿搜索
export interface SelfDraftSearchParams extends PageSearchParams {
type: ArticleTypeEnum
}
// 我的草稿 详情
export interface SelfDraftDetailDto {
id: number
......@@ -117,7 +135,7 @@ export interface AuditListItemDto {
showName: string
tagNameList: string[]
title: string
type: string
type: ArticleTypeEnum
viewCount: number
}
......@@ -126,6 +144,182 @@ export interface AuditListItemDto {
*/
export interface AuditArticleDto {
articleId: number
auditResult: Exclude<AuditStatusEnum, AuditStatusEnum.UNAUDITED>
auditResult: AuditStatusEnum
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>
\ 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="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
<?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 @@
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="relative">
<img
:src="articleDetail?.createUserAvatar"
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"
>
8
</div>
</div> -->
</div>
<div class="flex-1">
<div class="flex items-center gap-2">
<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"
>
资深前端工程师
</span>
</span> -->
</div>
<p class="text-sm text-gray-500 mt-1">
{{ dayjs((articleDetail?.createTime || 0) * 1000).format('YYYY-MM-DD HH:mm:ss') }}
· {{ articleDetail?.viewCount || 0 }} 阅读
</p>
</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 class="p-6">
<h1 class="text-2xl font-bold text-gray-900 mb-4 leading-tight">
{{ articleDetail?.title }}
</h1>
<!-- 文章内容 -->
<div class="prose prose-lg max-w-none">
<div class="text-gray-700 leading-relaxed space-y-4">
<div v-if="!isHtml" class="prose prose-lg max-w-none">
<div class="text-gray-700 leading-relaxed space-y-4 whitespace-pre-line text-17px">
{{ articleDetail?.content }}
</div>
......@@ -67,6 +100,8 @@
</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">
<span
......@@ -78,18 +113,76 @@
</span>
</div>
</div>
<!-- 富文本内容的 图片预览 -->
<el-image-viewer
v-if="showPreview"
:url-list="srcList"
show-progress
:teleported="true"
:initial-index="currentPreviewIndex"
@close="showPreview = false"
/>
</div>
</template>
<script setup lang="ts">
import dayjs from 'dayjs'
import { type ArticleItemDto } from '@/api'
import type { ArticleItemDto } from '@/api'
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<{
articleDetail: ArticleItemDto
isAudit: boolean // 是否是审核页面
}>()
const articleType = computed(() => {
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>
<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 @@
<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 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-icon><User /></el-icon>
<el-avatar
:size="48"
:src="userAvatar"
class="flex-shrink-0"
@click="() => console.log(form)"
>
<el-icon>
<IEpUser />
</el-icon>
</el-avatar>
<!-- 输入区域 -->
......@@ -24,14 +31,15 @@
<el-input
type="textarea"
:placeholder="textMap[type].content"
:rows="3"
:maxlength="500"
:rows="6"
:maxlength="maxLength"
show-word-limit
resize="none"
class="main-textarea"
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 class="mb-2">
......@@ -48,7 +56,12 @@
</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
class="relative w-20 h-20 rounded-lg overflow-hidden group"
......@@ -60,7 +73,7 @@
@click="handleDeleteImg(img)"
>
<el-icon class="text-white text-xs">
<Close />
<IEpClose />
</el-icon>
</div>
<el-image
......@@ -77,25 +90,41 @@
<div class="flex items-center justify-between pl-15">
<!-- 左侧工具按钮 -->
<div class="flex items-center gap-1">
<el-tooltip content="添加标签" placement="top">
<el-tooltip content="添加标签" placement="top" :visible="visibleTagTooltip">
<el-button
ref="tagButtonRef"
text
class="w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
@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-tooltip>
<!-- 隐藏上传文件的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-button
text
class="w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
@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-tooltip>
......@@ -107,7 +136,7 @@
<el-icon size="18"><VideoPlay /></el-icon>
</el-button>
</el-tooltip>
<el-tooltip content="添加附件" placement="top">
<el-button
text
......@@ -129,6 +158,7 @@
<el-button
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"
@click="handlePublish(ReleaseStatusTypeEnum.PUBLISH)"
>
......@@ -142,6 +172,10 @@
v-model:tagList="form.tagList"
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>
</template>
<!-- 发布实践 或者 问吧 的 发布框 -->
......@@ -149,18 +183,25 @@
import { useUserStore } from '@/stores'
import { storeToRefs } from 'pinia'
import SelectTagsDialog from './components/selectTagsDialog.vue'
import { useResetData } from '@/hooks'
import { useResetData, useUploadImg } from '@/hooks'
import { ArticleTypeEnum, ReleaseStatusTypeEnum, SendTypeEnum } from '@/constants'
import { useTagsStore } from '@/stores'
import { uploadFile } from '@/api'
import { Close } from '@element-plus/icons-vue'
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
const { type } = defineProps<{
const {
type,
isReal,
maxLength = 500,
} = defineProps<{
type: ArticleType
isReal: BooleanFlag
maxLength?: number
}>()
const textMap: Record<
......@@ -184,16 +225,42 @@ const { tagList } = storeToRefs(tagsStore)
const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore)
const userAvatar = computed(() => (isReal ? userInfo.value.avatar : userInfo.value.hiddenAvatar))
const selectTagsDialogRef =
useTemplateRef<InstanceType<typeof SelectTagsDialog>>('selectTagsDialogRef')
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({
title: '',
content: '',
imgUrl: [],
imgUrl: [] as string[],
releaseStatus: ReleaseStatusTypeEnum.PUBLISH,
mainTagId: '',
tagList: [],
......@@ -201,6 +268,10 @@ const [form, resetForm] = useResetData({
sendTime: '',
})
const { imgsStr, handleFileChange, handleDeleteImg, uploadPercent } = useUploadImg(
toRef(form.value, 'imgUrl'),
)
const mainTagText = computed(() => {
return tagList.value.find((tag) => tag.id === Number(form.value.mainTagId))?.title
})
......@@ -213,29 +284,15 @@ const handleAddTag = () => {
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 = () => {
if (!form.value.title) {
ElMessage.error('请输入实践标题')
return false
}
if (!form.value.content) {
ElMessage.error('请输入实践内容')
return false
}
if (!form.value.mainTagId) {
ElMessage.error('请选择主标签')
ElMessage.warning({
message: '请选择主标签',
offset: 200,
})
play()
visibleTagTooltip.value = true
return false
}
......@@ -246,7 +303,7 @@ const transformForm = (releaseStatus: ReleaseStatusTypeEnum): AddOrUpdatePractic
...form.value,
releaseStatus,
faceUrl: form.value.imgUrl[0] || '',
imgUrl: form.value.imgUrl.join(','),
imgUrl: imgsStr.value,
tagList: [form.value.mainTagId, ...form.value.tagList].map((item, index) => ({
sort: index,
tagId: Number(item),
......
......@@ -65,13 +65,17 @@ import { useTagsStore } from '@/stores/tags'
import { storeToRefs } from 'pinia'
import type { TagItemDto } from '@/api/tag/types'
import type { SelectTagProps } from './types'
const { maxSelectedTags = 1, filterTagsFn } = defineProps<SelectTagProps>()
const { maxSelectedTags = 1, filterTagsFn, tagType = 'culture' } = defineProps<SelectTagProps>()
const emit = defineEmits<{
selected: [tag?: TagItemDto]
}>()
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(() => {
if (filterTagsFn) {
......@@ -155,10 +159,4 @@ const toggleTag = (tagId: number) => {
addTag(tagId)
}
}
// onMounted(() => {
// if (tagList.value.length === 0) {
// tagsStore.fetchTagList()
// }
// })
</script>
......@@ -3,4 +3,5 @@ import type { TagItemDto } from '@/api/tag/types'
export type SelectTagProps = {
maxSelectedTags?: number
filterTagsFn?: (tags: TagItemDto[]) => TagItemDto[]
tagType?: 'culture' | 'related_scenarios'
}
......@@ -13,13 +13,14 @@
:multiple="multiple"
:limit="limit"
class="custom-upload"
v-loading="uploadPercent > 0"
:element-loading-text="uploadPercent + '%'"
>
<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-dialog v-model="dialogVisible">
<img w-full :src="dialogImageUrl" alt="Preview Image" />
</el-dialog>
</template>
<script lang="ts" setup generic="T extends string | string[]">
......@@ -41,14 +42,13 @@ const fileList = ref<UploadUserFile[]>([])
const uploadRef = useTemplateRef('uploadRef')
const dialogImageUrl = ref('')
const dialogVisible = ref(false)
const uploadPercent = ref(0)
const isArrayType = computed(() => Array.isArray(modelValue.value))
const hasReachedLimit = computed(() => fileList.value.length >= props.limit)
const showUploadBtn = computed(() => (hasReachedLimit.value ? 'none' : 'flex'))
const isInternalUpdate = ref(false)
const uploadProgress = ref(0)
const parseModelValueToUrls = (value: T): string[] => {
if (!value) return []
return Array.isArray(value) ? value.filter(Boolean) : (value as string).split(',').filter(Boolean)
......@@ -112,7 +112,6 @@ const handleExceed: UploadProps['onExceed'] = (uploadFiles) => {
const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) => {
console.log('uploadFiles', uploadFiles)
if (uploadFiles.length > props.limit) {
debugger
ElMessage.error(`最多上传 ${props.limit} 个文件`)
const index = fileList.value.findIndex((file) => file.uid === uploadFile.uid)
if (index !== -1) {
......@@ -127,15 +126,21 @@ const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) =>
try {
let fileIndex = fileList.value.findIndex((file) => file.uid === uid)
if (fileIndex !== -1) {
fileList.value[fileIndex].status = 'uploading'
fileList.value[fileIndex]!.status = 'uploading'
}
const { data } = await uploadFileApi(uploadFile.raw, (progress) => {
console.log('progress', progress)
const { promise } = uploadFileApi(uploadFile.raw, {
onProgress: (progress) => {
console.log('progress', progress)
uploadPercent.value = progress
},
})
const data = await promise
console.log('data', data)
const url = data.fileUrl || data.data[0].filePath
const name = data.fileName || data.data[0].finalName
const url = data.filePath || ''
const name = data.finalName || ''
fileIndex = fileList.value.findIndex((file) => file.uid === uid)
......@@ -158,11 +163,13 @@ const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) =>
if (fileIndex !== -1) {
fileList.value.splice(fileIndex, 1)
}
} finally {
uploadPercent.value = 0
}
}
}
const handleBeforeRemove: UploadProps['beforeRemove'] = (uploadFile) => {
const handleBeforeRemove: UploadProps['beforeRemove'] = () => {
return ElMessageBox.confirm('确定要删除这个文件吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
......
......@@ -13,24 +13,24 @@
class="upload-area"
>
<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">
<p>拖拽视频文件到此处,或点击选择文件</p>
<p class="upload-hint">支持 MP4、AVI、MOV 格式,文件大小不超过 500MB</p>
<p class="upload-hint">支持 MP4、AVI、MOV 格式,文件大小不超过 {{ maxSize }}MB</p>
</div>
</div>
<!-- 上传进度 -->
<div v-if="uploading" class="upload-progress">
<div class="progress-info">
<el-icon class="progress-icon"><VideoPlay /></el-icon>
<el-icon class="progress-icon"><IEpVideoPlay /></el-icon>
<div class="progress-details">
<p class="file-name">{{ currentFile?.name }}</p>
<p class="progress-text">正在上传...</p>
</div>
</div>
<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>
</div>
......@@ -40,7 +40,7 @@
<div class="video-preview">
<video
: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"
muted
></video>
......@@ -60,7 +60,7 @@
<!-- 错误提示 -->
<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>
<el-button type="text" @click="retryUpload">重试</el-button>
</div>
......@@ -82,7 +82,7 @@ interface VideoInfo {
fileId?: string
}
const { maxSize = 500, acceptFormats = ['mp4', 'avi', 'mov', 'wmv', 'flv'] } =
const { maxSize = 1000, acceptFormats = ['mp4', 'avi', 'mov', 'wmv', 'flv'] } =
defineProps<UploadVideoProps>()
const modelValue = defineModel<string>('modelValue', { required: true })
......@@ -163,6 +163,7 @@ const handleFileChange = async (file: UploadFile) => {
await startUpload()
}
let cancelUploadController = () => {}
// 开始上传
const startUpload = async () => {
if (!currentFile.value) return
......@@ -173,9 +174,14 @@ const startUpload = async () => {
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)
// 获取视频元数据
......@@ -183,15 +189,15 @@ const startUpload = async () => {
// 根据你的 API 返回结构调整
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,
size: currentFile.value.size,
duration: metadata.duration,
resolution: metadata.resolution,
poster: data.fileUrl,
fileId: data.fileId,
poster: data.filePath || '',
fileId: data.fileId || '',
}
videoInfo.value = videoData
......@@ -215,6 +221,7 @@ const startUpload = async () => {
// 取消上传
const cancelUpload = () => {
cancelUploadController()
uploading.value = false
uploadProgress.value = 0
ElMessage.info('已取消上传')
......@@ -233,6 +240,20 @@ const retryUpload = () => {
uploadError.value = ''
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>
<style scoped>
......@@ -358,7 +379,7 @@ const retryUpload = () => {
height: 80px;
border-radius: 8px;
overflow: hidden;
background: #000;
/* background: #000; */
}
.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 {
export const app_config: { [key: string]: IConfig } = {
// 正式环境
production: {
baseUrl: 'https://culture.yswg.com.cn:8089',
loginType: 3,
wxRedirect: 'oa3.yswg.com.cn',
baseUrl: 'http://culture.yswg.com.cn:8089',
loginType: 1, // 3
wxRedirect: 'culture.yswg.com.cn:3000',
},
// 测试环境 暂时无测试环境部署
test: {
baseUrl: 'http://192.168.2.55:8089', // 首拥本地
loginType: 1,
wxRedirect: 'oatest.yswg.com.cn:3457',
wxRedirect: '',
},
// 开发环境
development: {
// baseUrl: 'http://oa.yswg.com.cn:8082', // 正式环境
baseUrl: '/api1', // 线上测试机
// baseUrl: 'http://192.168.2.110:8082', // 线上测试机
// baseUrl: 'http://192.168.2.85:8080', // 洋倍
// baseUrl: 'http://192.168.2.12:8084', // 立鹏
// baseUrl: 'http://192.168.2.55:8089', // 首拥
baseUrl: 'http://culture.yswg.com.cn:8089', // 线上测试机
// baseUrl: 'http://192.168.2.168:8089', // 立鹏本地/
// baseUrl: 'http://192.168.2.55:8089', // 首拥本地
loginType: 1,
wxRedirect: 'oatest.yswg.com.cn:3457',
wxRedirect: '',
},
}
......@@ -89,3 +89,11 @@ export enum AuditStatusEnum {
// 驳回审核
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 = [
......@@ -43,7 +49,7 @@ export const articleTypeListOptions: { label: string; value: ArticleTypeEnum }[]
value: ArticleTypeEnum.VIDEO,
},
{
label: '问',
label: '问',
value: ArticleTypeEnum.QUESTION,
},
{
......@@ -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 }[] = [
{
......@@ -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 = [
{
......
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'
export * from './usePageSearch'
export * from './useScrollTop'
export * from './useHintAnimation'
export * from './useUploadImg'
......@@ -7,7 +7,7 @@ export const useHintAnimation = (
el: MaybeRef<HTMLElement | null>,
{ classes = [], duration = 200 }: UseHintAnimationOptions = {},
) => {
let timer: number | null = null
let timer: NodeJS.Timeout | null = null
const triggerAnimation = () => {
const dom = unref(el)
if (!dom) return
......
......@@ -109,6 +109,13 @@ export function usePageSearch<
search()
}
// 清空搜索数据和列表
const clear = () => {
list.value = []
total.value = 0
resetSearchParams()
}
if (immediate) {
onMounted(search)
}
......@@ -123,5 +130,6 @@ export function usePageSearch<
goToPage,
changePageSize,
refresh,
clear,
}
}
......@@ -19,36 +19,66 @@ function ScrollTopComp(_: any, { emit }: SetupContext<Events>) {
)
}
// 顺便兼容下多个的
export const useScrollTop = (
el: MaybeRef<HTMLElement | null | Window>,
el: MaybeRef<HTMLElement | null | Window | HTMLElement[] | [Window]>,
options: { compatFixedHeader?: boolean } = {},
) => {
const { compatFixedHeader = true } = options
const handleBackTop = () => {
const dom = unref(el)
if (!dom) return
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({
behavior: 'smooth',
block: 'start',
})
}
const handleBackTop = (currentIndex: number = 0): Promise<void> => {
return new Promise((resolve) => {
const initDoms = unref(el)
if (!initDoms) {
resolve()
return
}
let doms = []
if (!Array.isArray(initDoms)) {
doms = [initDoms]
} else {
doms = initDoms
}
const finish = () => {
console.log('scrollend')
resolve()
}
// 手动添加一次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 {
......
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 }) => {
})
const formRef = ref<InstanceType<typeof ElForm>>()
const rules = {
title: [{ required: true, message: '请输入实践标题', trigger: 'blur' }],
content: [{ required: true, message: '请输入实践内容', trigger: 'blur' }],
title: [{ required: true, message: '请输入专栏标题', trigger: 'blur' }],
content: [{ required: true, message: '请输入专栏内容', trigger: 'blur' }],
releaseStatus: [{ required: true, message: '请选择发布时间', trigger: 'blur' }],
mainTagId: [{ required: true, message: '请选择主标签', trigger: 'blur' }],
sendType: [{ required: true, message: '请选择发布类型', trigger: 'blur' }],
......@@ -40,6 +40,7 @@ export default defineComponent((_, { expose }) => {
{ required: true, message: '请选择专栏栏目', trigger: 'blur', type: 'number', min: 1 },
],
isRecommend: [{ required: true, message: '请选择是否推荐', trigger: 'blur' }],
imgUrl: [{ required: true, message: '请上传图片', trigger: 'blur' }],
}
const transformForm = (releaseStatus: ReleaseStatusTypeEnum): AddOrUpdateColumnDto => {
......@@ -103,7 +104,7 @@ export default defineComponent((_, { expose }) => {
<el-input
v-model={form.value.content}
type="textarea"
placeholder="分享你的企业文化实践实例"
placeholder="请输入专栏内容"
rows={6}
maxlength={1000}
show-word-limit
......
......@@ -27,8 +27,8 @@ export default defineComponent((_, { expose }) => {
})
const formRef = ref<InstanceType<typeof ElForm>>()
const rules = {
title: [{ required: true, message: '请输入实践标题', trigger: 'blur' }],
content: [{ required: true, message: '请输入实践内容', trigger: 'blur' }],
title: [{ required: true, message: '请输入专访标题', trigger: 'blur' }],
content: [{ required: true, message: '请输入专访内容', trigger: 'blur' }],
releaseStatus: [{ required: true, message: '请选择发布时间', trigger: 'blur' }],
mainTagId: [{ required: true, message: '请选择主标签', trigger: 'blur' }],
sendType: [{ required: true, message: '请选择发布类型', trigger: 'blur' }],
......@@ -37,6 +37,7 @@ export default defineComponent((_, { expose }) => {
{ required: true, message: '请选择专访栏目', trigger: 'blur', type: 'number', min: 1 },
],
isRecommend: [{ required: true, message: '请选择是否推荐', trigger: 'blur' }],
imgUrl: [{ required: true, message: '请上传图片', trigger: 'blur' }],
}
const transformForm = (releaseStatus: ReleaseStatusTypeEnum): AddOrUpdateInterviewDto => {
......@@ -100,7 +101,7 @@ export default defineComponent((_, { expose }) => {
<el-input
v-model={form.value.content}
type="textarea"
placeholder="分享你的企业文化实践实例"
placeholder="请输入专访内容"
rows={6}
maxlength={1000}
show-word-limit
......
......@@ -5,7 +5,7 @@
storage-type="session"
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
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 @@
<div
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 class="absolute bottom-2 left-3 right-3 h-1 bg-gray-200 rounded-full overflow-hidden">
<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>
......@@ -67,12 +70,54 @@
<script setup lang="ts">
import { UseDraggable as Draggable } from '@vueuse/components'
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 CONTAINER_WIDTH = 130,
CONTAINER_HEIGHT = 170,
const { height } = useWindowSize()
const CONTAINER_HEIGHT = 170,
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 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>
......@@ -23,7 +23,6 @@ export default defineComponent((_, { expose }) => {
const rules = {
title: [{ required: true, message: '请输入实践标题', trigger: 'blur' }],
content: [{ required: true, message: '请输入实践内容', trigger: 'blur' }],
imgUrl: [{ required: true, message: '请上传实践图片', trigger: 'change' }],
releaseStatus: [{ required: true, message: '请选择发布时间', trigger: 'blur' }],
mainTagId: [{ required: true, message: '请选择主标签', trigger: 'blur' }],
sendType: [{ required: true, message: '请选择发布类型', trigger: 'blur' }],
......@@ -93,12 +92,12 @@ export default defineComponent((_, { expose }) => {
type="textarea"
placeholder="分享你的企业文化实践实例"
rows={6}
maxlength={1000}
maxlength={2000}
show-word-limit
class="content-input"
/>
</el-form-item>
<el-form-item label="图片" prop="imgUrl">
<el-form-item label="图片">
{/* @ts-ignore */}
<UploadFile v-model={form.value.imgUrl} />
</el-form-item>
......
......@@ -41,10 +41,24 @@ import type { Component } from 'vue'
import { addOrUpdateArticle, addOrUpdatePractice } from '@/api'
import { ArticleTypeEnum, ReleaseStatusTypeEnum } from '@/constants'
import LoadingComponent from '@/components/common/LoadingComponent/index.vue'
const typeMap: Record<
ArticleTypeEnum,
{ title: string; component: Component; api?: (data: any) => Promise<any> }
> = {
interface ApiMap {
[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]: {
title: '视频',
component: defineAsyncComponent({
......@@ -148,7 +162,7 @@ const handleClosed = () => {
const handleSubmit = async (releaseStatus: ReleaseStatusTypeEnum) => {
loading.value = true
try {
const formData = await formComponentRef.value?.getValidatedFormData(releaseStatus)
const formData = (await formComponentRef.value?.getValidatedFormData(releaseStatus)) as any
if (!formData) return
console.log(formData)
await typeMap[articleType.value].api?.({
......
......@@ -14,10 +14,13 @@
<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="搜索">
<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 />
</el-icon>
</template>
......@@ -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"
@click="router.push('/userPage')"
>
<!-- 默认展示匿名头像 -->
<img
class="w-8 h-8 object-contain flex-shrink-0 rounded-full"
:src="userInfo?.avatar"
:src="userInfo?.hiddenAvatar"
alt="个人中心"
/>
<span class="ml-2 text-sm text-gray-700 whitespace-nowrap hidden lg:inline"
......@@ -71,8 +75,18 @@
<el-dropdown-menu>
<el-dropdown-item :command="ArticleTypeEnum.POST">帖子</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.INTERVIEW">专访</el-dropdown-item>
<el-dropdown-item :command="ArticleTypeEnum.VIDEO">视频</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>
</template>
</el-dropdown>
......@@ -81,7 +95,7 @@
</div>
<div class="flex-1 w-full flex items-center justify-center">
<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 }">
<transition name="fade" mode="out-in">
......@@ -94,7 +108,7 @@
</div>
</div>
</div>
<OnlineTime />
<OnlineTime v-if="showOnlineTime" />
<PublishDialog ref="PublishDialogRef" />
</template>
......@@ -112,12 +126,15 @@ const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore)
const router = useRouter()
const route = useRoute()
const search = ref('')
const PublishDialogRef = useTemplateRef<InstanceType<typeof PublishDialog>>('PublishDialogRef')
console.log(route)
const showSearchInupt = computed(() => route.path !== '/searchPage')
// 获取二级路由的 key
const getSecondLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => {
// 取第一级作为key homePage
const pathSegments = route.path.split('/').filter(Boolean)
// console.log(route)
// console.log(pathSegments)
......@@ -125,18 +142,35 @@ const getSecondLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => {
const key = Object.keys(route.params).length
? pathSegments.slice(0, 2).join('/')
: pathSegments.slice(0, 1).join('/')
// console.log(key, '*********************')
return key
}
const handlePost = async (type: string) => {
// router.push(command)
PublishDialogRef.value?.open(type)
const notShowPath = ['/videoDetail', '/articleDetail', '/questionDetail']
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)
</script>
<style lang="scss" scoped>
.layout-culture {
padding-top: 52px;
.header {
min-height: 52px;
color: #212121;
......@@ -145,11 +179,13 @@ const isDropdownHover = ref(false)
background-size: 100% 100%;
background-repeat: no-repeat;
}
.container {
.main-container {
.tabs-container {
background: linear-gradient(90deg, #16cdea 0%, #d680ff 100%);
}
.right {
.common-box {
padding: 15px;
......@@ -157,6 +193,7 @@ const isDropdownHover = ref(false)
border-radius: 10px;
border: 1px solid #efe7dc;
}
.common-btn {
display: flex;
align-items: center;
......@@ -178,6 +215,7 @@ const isDropdownHover = ref(false)
opacity: 0;
transform: translateY(10px);
}
.fade-enter-active,
.fade-leave-active {
transition: all 0.3s ease;
......
......@@ -10,13 +10,15 @@ import 'virtual:uno.css'
// 注册svg
import 'virtual:svg-icons-register'
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)
app.use(createPinia())
app.use(router)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
// 全局组件挂载
app.component('SvgIcon', SvgIcon)
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 { saveScrollPosition } from './scrollStorage'
import { parseCode, parseIsCodeLogin, parseIsCutEmail } from '@/utils/wxUtil'
import { parseCode } from '@/utils/wxUtil'
import { useUserStore } from '@/stores'
import { fa } from 'element-plus/es/locales.mjs'
// 白名单
const WHITE_LIST: string[] = ['/aa']
export function registerRouterGuard(router: Router) {
export function registerRouterGuards(router: Router) {
router.beforeEach(async (to, from) => {
// console.log('to', to)
// console.log('from', from)
// 保存当前页面的滚动位置
if (from.fullPath) {
saveScrollPosition(from.fullPath, window.scrollY)
......@@ -18,18 +19,23 @@ export function registerRouterGuard(router: Router) {
return true
}
const code = parseCode()
const code = parseCode(to.fullPath)
// code是否来自企业微信 1 不是 0 是 2 开发人员登录方式
const isCodeLogin = parseIsCodeLogin()
const cutEmail = parseIsCutEmail()
console.log(code, isCodeLogin, cutEmail)
// const isCodeLogin = parseIsCodeLogin()
// const cutEmail = parseIsCutEmail()
// console.log(code, isCodeLogin, cutEmail)
const userStore = useUserStore()
if (code) {
console.log('code', code)
await userStore.getUserInfoByCode(code)
return true
// console.log('code', code)
await userStore.getUserInfoByCode({
code,
isCodeLogin: 0,
})
return {
path: to.path,
replace: true,
}
} else {
return true
}
......
import { createRouter, createWebHistory } from 'vue-router'
import layoutCulture from '@/layoutCulture/index.vue'
import { scrollBehavior } from './scrollStorage'
import { registerRouterGuard } from './regards'
const routes = [
{
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: '积分商城——商品分发' },
},
],
},
]
import { registerRouterGuards } from './guards'
import { constantsRoute } from './route'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
// 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 })
// })
// },
routes: constantsRoute,
scrollBehavior,
})
// 在路由离开前保存当前滚动位置
// router.beforeEach((to, from, next) => {
// // 保存当前页面的滚动位置
// if (from.fullPath) {
// scrollPositionMap.set(from.fullPath, window.scrollY)
// }
// next()
// })
registerRouterGuard(router)
registerRouterGuards(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 {
*/
export const scrollBehavior: RouterScrollBehavior = (to, from, savedPosition) => {
return new Promise((resolve) => {
console.log('触发路由滚动')
// 1. 如果有浏览器保存的位置(前进/后退),优先使用
if (savedPosition) {
resolve(savedPosition)
return
}
// 2. 如果有锚点,滚动到锚点
// 2. 如果有锚点, 约定 默认不滚动 然后在具体的组件里面onActivated里面 或者watch处理滚动逻辑 以及漫游逻辑等
if (to.hash) {
resolve({
el: to.hash,
behavior: 'smooth', // 平滑滚动
})
// console.log(to.hash, window.scrollY)
// resolve({
// top: window.scrollY,
// })
return
}
// 3. 检查是否有保存的滚动位置
const savedScrollY = scrollPositionMap.get(to.fullPath)
if (savedScrollY !== undefined) {
resolve({
top: savedScrollY,
behavior: 'smooth',
})
setTimeout(() => {
resolve({
top: savedScrollY,
behavior: 'smooth',
})
}, 300)
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