Commit 1495325f by 王立鹏

Merge branch 'feature/21170-YAYA文化岛官方账号ip显示问题' into 'master'

Feature/21170 yaya文化岛官方账号ip显示问题

See merge request !19
parents 7f921885 cdd10067
# .
# 企业文化平台前端
基于 `Vue 3 + Vite + TypeScript` 的企业文化平台前端项目,包含前台内容展示、用户中心、内容发布、积分商城、限时竞拍等,以及后台内容与配置管理。
## 项目概览
- 前台业务:
- 首页内容流
- YA 文化 / 问吧等频道页
- 帖子、视频、专栏、专访、问吧、实践、案例详情
- 用户中心、他人主页、我的互动内容
- 视频/长文/案例发布
- 积分商城、竞拍、私信等功能页
- 后台业务:
- 标签、轮播图、案例库管理
- 内容管理:专栏 / 专访 / 视频 / 问吧
- 配置管理:商城、竞拍、每日抽奖
## 技术栈
- 框架:`Vue 3`
- 构建工具:`Vite`
- 语言:`TypeScript`
- 路由:`vue-router`
- 状态管理:`Pinia`
- UI 组件:`Element Plus`
- 原子化样式:`UnoCSS`
- 组合式工具:`VueUse`
- 富文本编辑:`WangEditor`
- HTTP 请求:`Axios`
- 提示通知:`Notivue`
- SVG 图标:`vite-plugin-svg-icons`
- 自动导入:`unplugin-auto-import``unplugin-vue-components`
## 运行环境
- Node.js:`^20.19.0 || >=22.12.0`
- 包管理器:推荐 `pnpm`
## 安装依赖
```bash
pnpm install
```
This template should help get you started developing with Vue 3 in Vite.
## 本地开发
## Recommended IDE Setup
```bash
pnpm dev
```
默认开发端口:
```bash
http://localhost:3000
```
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## 常用脚本
## Recommended Browser Setup
### 启动开发环境
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
- Firefox:
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
```bash
pnpm dev
```
## Type Support for `.vue` Imports in TS
### 类型检查
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
```bash
pnpm type-check
```
## Customize configuration
### 代码检查并自动修复
See [Vite Configuration Reference](https://vite.dev/config/).
```bash
pnpm lint
```
## Project Setup
### 代码格式化
```sh
pnpm install
```bash
pnpm format
```
### Compile and Hot-Reload for Development
### 本地预览构建结果
```sh
pnpm dev
```bash
pnpm preview
```
### Type-Check, Compile and Minify for Production
### 默认构建
```sh
```bash
pnpm build
```
### Lint with [ESLint](https://eslint.org/)
### 按环境构建
```sh
pnpm lint
```bash
pnpm build:dev
pnpm build:test
pnpm build:prod
```
### 发布脚本
```bash
pnpm deploy:test
pnpm deploy:prod
pnpm deploy:prod:update-info
```
## 环境变量
项目使用 `.env.local` 管理本地环境变量。常见变量如下:
```bash
VITE_AI_API_URL=
MY_OPENAI_API_KEY=
MY_OPENAI_BASE_URL=
BACKEND_API_URL=
DEPLOY_PROD_HOST=
DEPLOY_PROD_PORT=
DEPLOY_PROD_USERNAME=
DEPLOY_PROD_PASSWORD=
DEPLOY_TEST_HOST=
DEPLOY_TEST_PORT=
DEPLOY_TEST_USERNAME=
DEPLOY_TEST_PASSWORD=
```
说明:
- `VITE_` 前缀变量会注入前端运行时。
- `BACKEND_API_URL` 用于业务接口请求地址。
- `MY_OPENAI_*` 用于本地 AI 代理 / OpenAI 兼容接口。
- `DEPLOY_*` 用于部署脚本连接服务器。
建议:
- 不要在 README 中记录真实密钥和密码。
- 团队协作时建议补一个 `.env.example` 作为示例模板。
## 目录结构
```text
.
├─ deploy/ # 测试 / 正式环境部署脚本
├─ public/ # 公共静态资源
├─ server/ # 本地服务或辅助服务代码
├─ src/
│ ├─ api/ # 接口定义与请求封装
│ ├─ assets/ # 图片、SVG、基础样式
│ ├─ components/ # 通用组件
│ ├─ constants/ # 常量、枚举、选项配置
│ ├─ hooks/ # 组合式 hooks
│ ├─ layoutCulture/ # 前台整体布局
│ ├─ plugins/ # Vite / 业务插件
│ ├─ router/ # 路由与守卫
│ ├─ stores/ # Pinia stores
│ ├─ style/ # 全局样式
│ ├─ utils/ # 工具函数、请求、存储、版本检测等
│ ├─ views/ # 页面级视图
│ ├─ App.vue
│ ├─ config.ts
│ └─ main.ts
├─ vite.config.ts # Vite 配置
├─ uno.config.ts # UnoCSS 配置
├─ tsconfig*.json # TypeScript 配置
└─ package.json
```
## 路由结构
### 前台主路由
- `/homePage/homeTab`
- `/homePage/yaTab`
- `/homePage/askTab`
- `/userPage/*`
- `/videoDetail/:id`
- `/articleDetail/:id`
- `/questionDetail/:id`
- `/publishVideo`
- `/publishCase`
- `/publishLongArticle/:type`
- `/pointsStore`
- `/auction`
### 后台路由
- `/backend/tags`
- `/backend/carousel`
- `/backend/caseManage`
- `/backend/goodsDistribution`
- `/backend/contentsMenu/*`
- `/backend/settingsMenu/*`
- `/backend/columnsMenu/*`
## 工程特性
- 基于 `Element Plus` 组件体系构建
- 使用 `UnoCSS` 进行原子化样式开发
- 使用 `AutoImport``Components` 自动导入 Vue / Element Plus 能力
- 支持 SVG 图标自动注册
- 构建阶段按依赖进行 `manualChunks` 拆包
- 生产环境启用版本轮询更新逻辑
## 开发建议
### 推荐编辑器
- `VS Code`
- 安装插件:
- `Vue - Official`
- `ESLint`
- `Prettier`
- `UnoCSS`
### 样式建议
- 优先使用 `UnoCSS`
- 页面结构复杂时再配合少量 `scoped style`
- 公共视觉元素优先沉淀到 `src/components/common`
### 代码组织建议
- 页面接口统一放在 `src/api`
- 复用逻辑统一抽到 `src/hooks`
- 通用方法放在 `src/utils`
- 状态共享走 `Pinia`
## 构建与部署说明
- `pnpm build:*` 用于不同环境构建
- `deploy/` 目录中维护测试与正式环境发布脚本
- 发布前请确认:
- 环境变量配置正确
- 当前分支代码已通过基本功能验证
- 需要的服务器连接信息已更新
......@@ -40,6 +40,8 @@ export interface AddOrUpdatePostDto {
imgUrl?: string
sendTime?: string
type?: ArticleTypeEnum.POST
// 1是隐藏
regionHide?: BooleanFlag
}
interface AddOrUpdateColumnBase {
......@@ -205,6 +207,7 @@ export interface ArticleItemDto {
videoLocation: VideoPositionEnum
articleVideoUrl: string
region: string
regionHide: BooleanFlag
recommendSort: number
isOfficialAccount: BooleanFlag
}
......
import type { TagItemDto } from '@/api/case/types'
import type { TagItemDto } from '@/api/tag/types'
import { UsageStatusEnum, AuditStatusEnum } from '@/constants'
import type { PageSearchParams } from '@/utils/request/types'
......
......@@ -9,7 +9,7 @@ export interface BackendShopListSearchParams extends PageSearchParams {
export interface BackendShopItemDto {
createTime: number
deliveryInfo: string
id: number
id?: number
imageUrl: string
isDelete: null
issueTime: number
......
......@@ -116,9 +116,7 @@
<p class="text-sm text-gray-500 mt-1">
{{ dayjs((articleDetail?.createTime || 0) * 1000).format('YYYY-MM-DD HH:mm:ss') }}
· {{ articleDetail?.viewCount || 0 }} 阅读
<span v-if="!articleDetail?.isOfficialAccount"
>· {{ articleDetail?.region || 0 }}</span
>
<span v-if="articleDetail?.region">· {{ articleDetail?.region }}</span>
</p>
</div>
<!-- 再次编辑按钮 -->
......
......@@ -174,8 +174,7 @@
<span
>{{ dayjs(item.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }}
</span>
<span>·</span>
<span class="text-gray-500">{{ item.region }}</span>
<span v-if="item.region" class="text-gray-500">·{{ item.region }}</span>
</span>
<div class="flex gap-2 items-center hover:text-blue-500 transition-colors">
<div
......@@ -253,8 +252,9 @@
dayjs(child.createTime * 1000).format('YYYY-MM-DD HH:mm:ss')
}}
</span>
<span>·</span>
<span class="text-gray-500">{{ child.region }}</span>
<span v-if="child.region" class="text-gray-500"
>·{{ child.region }}</span
>
</span>
<div class="flex gap-2 items-center hover:text-blue-500">
<div
......
......@@ -73,8 +73,10 @@
</div>
<div class="text-sm text-gray-500 flex items-center gap-2">
<span>{{ dayjs(parentComment.createTime * 1000).format('MM-DD HH:mm') }}</span> ·
<span class="text-xs text-gray-500">{{ parentComment.region }}</span>
<span>{{ dayjs(parentComment.createTime * 1000).format('MM-DD HH:mm') }}</span>
<span v-if="parentComment.region" class="text-xs text-gray-500"
>·{{ parentComment.region }}</span
>
</div>
</div>
</div>
......@@ -145,8 +147,8 @@
</div>
<div class="text-[14px] text-gray-500 flex items-center gap-2">
<span>{{ dayjs(item.createTime * 1000).format('MM-DD HH:mm') }}</span> ·
<span class="text-xs text-gray-500">{{ item.region }}</span>
<span>{{ dayjs(item.createTime * 1000).format('MM-DD HH:mm') }}</span>
<span v-if="item.region" class="text-xs text-gray-500">·{{ item.region }}</span>
</div>
<!-- 内嵌回复框 -->
......@@ -250,7 +252,7 @@ const emit = defineEmits<{
// Store
const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore)
const currentUserAvatar = computed(() => userInfo.value.hiddenAvatar)
const currentUserAvatar = computed(() => userInfo.value.avatar)
// State
const visible = ref(false)
......
......@@ -84,9 +84,11 @@
<span class="text-xs text-gray-400 hidden sm:inline">{{
dayjs(item.createTime * 1000).format('YYYY-MM-DD HH:mm')
}}</span>
<template v-if="item.region">
<div class="hidden sm:block w-1 h-1 bg-gray-300 rounded-full mx--1"></div>
<!-- 地区 -->
<span class="text-xs text-gray-400 hidden sm:inline">{{ item.region }}</span>
</template>
<div class="hidden sm:block w-1 h-1 bg-gray-300 rounded-full mx--1"></div>
<!-- 阅读量 -->
<span class="text-xs text-gray-400 hidden sm:inline">{{ item.viewCount }}阅读</span>
......
......@@ -23,10 +23,15 @@ export default function ExchangeContent(
<div class="py-4">
{/* 商品信息卡片 */}
<div class="flex gap-4 p-4 bg-gray-50 rounded-xl mb-5">
<div class="w-24 h-24 bg-white rounded-xl overflow-hidden shadow-sm shrink-0
flex items-center justify-center p-2">
<img src={item.imageUrl} alt={item.name}
class="max-w-full max-h-full object-contain rounded-lg" />
<div
class="w-24 h-24 bg-white rounded-xl overflow-hidden shadow-sm shrink-0
flex items-center justify-center p-2"
>
<img
src={item.imageUrl}
alt={item.name}
class="max-w-full max-h-full object-contain rounded-lg"
/>
</div>
<div class="flex flex-col justify-center min-w-0 gap-1.5">
<div class="text-base font-semibold text-gray-800 line-clamp-2">{item.name}</div>
......@@ -82,7 +87,9 @@ export default function ExchangeContent(
<span class="text-sm text-gray-500">
合计扣除
{modelValue.num > 1 && (
<span class="text-gray-400 text-xs ml-1">({item.price} × {modelValue.num})</span>
<span class="text-gray-400 text-xs ml-1">
({item.price} × {modelValue.num})
</span>
)}
</span>
<div class="flex items-baseline gap-0.5">
......
......@@ -302,6 +302,18 @@
/>
</el-form-item>
</div>
<!-- 针对官方账号新增是否显示ip -->
<template v-if="userInfo.isOfficialAccount">
<el-form-item label="IP信息" prop="regionHide">
<el-switch
v-model="form.regionHide"
:active-value="BooleanFlag.YES"
:inactive-value="BooleanFlag.NO"
active-text="隐藏地区"
inactive-text="显示地区"
/>
</el-form-item>
</template>
</el-form>
<!-- 抽屉底部按钮 -->
......@@ -389,7 +401,7 @@ const [form, resetForm] = useResetData({
sendType: SendTypeEnum.IMMEDIATE,
sendTime: '',
releaseStatus: ReleaseStatusTypeEnum.PUBLISH,
regionHide: BooleanFlag.NO,
isPushAll: BooleanFlag.YES,
// 推送设置
pushType: SendTypeEnum.IMMEDIATE,
......@@ -431,7 +443,7 @@ const rules: Record<string, FormItemRule[]> = {
isRecommend: [{ required: true, message: '是否推荐', trigger: 'trigger' }],
isRelateColleague: [{ required: true, message: '是否同步同事吧', trigger: 'trigger' }],
relateColumnId: [{ required: true, message: '请选择对应的栏目', trigger: 'trigger' }],
regionHide: [{ required: true, message: '请选择是否隐藏IP信息', trigger: 'trigger' }],
// 推送设置
pushType: [{ required: true, message: '请选择推送类型', trigger: 'trigger' }],
pushTime: [{ required: true, message: '请选择推送时间', trigger: 'trigger' }],
......@@ -587,6 +599,7 @@ onActivated(async () => {
sendTime,
tagIdList,
regionHide,
} = data
form.value = {
......@@ -599,6 +612,7 @@ onActivated(async () => {
sendType,
sendTime,
id,
regionHide,
}
// 2回显主副标签
......
......@@ -136,6 +136,23 @@
<!-- 新增推送设置 -->
<template v-if="userInfo.isOfficialAccount">
<!-- 是否隐藏地区 -->
<div class="mb-8">
<el-form-item prop="regionHide">
<label class="block text-sm font-semibold text-gray-700 mb-3">
是否隐藏地区
</label>
<div class="w-full flex items-center gap-2">
<el-switch
v-model="form.regionHide"
:active-value="BooleanFlag.YES"
:inactive-value="BooleanFlag.NO"
active-text="隐藏地区"
inactive-text="显示地区"
/>
</div>
</el-form-item>
</div>
<div class="mb-8">
<el-form-item>
<label class="block text-sm font-semibold text-gray-700 mb-3"> 推送对象 </label>
......@@ -404,7 +421,7 @@ const [form, resetData] = useResetData({
faceUrl: '', // 封面URL
videoDuration: '',
relateColumnId: '' as string | number,
regionHide: BooleanFlag.NO,
isPushAll: BooleanFlag.YES,
// 推送设置
pushType: SendTypeEnum.IMMEDIATE,
......@@ -542,7 +559,7 @@ const rules = {
mainTagId: [{ required: true, message: '请选择主标签', trigger: 'change' }],
faceUrl: [{ required: true, message: '请选择视频封面', trigger: 'change' }],
relateColumnId: [{ required: true, message: '请选择视频栏目', trigger: 'change' }],
regionHide: [{ required: true, message: '请选择是否隐藏地区', trigger: 'trigger' }],
// 推送设置
pushType: [{ required: true, message: '请选择推送类型', trigger: 'trigger' }],
pushTime: [{ required: true, message: '请选择推送时间', trigger: 'trigger' }],
......
......@@ -123,8 +123,8 @@
<span class="text-sm text-slate-500 flex items-center gap-1">
{{ dayjs(questionDetail.createTime * 1000).format('YYYY-MM-DD HH:mm') }} ·
<span
v-if="questionDetail?.region"
class="text-sm text-slate-500 flex items-center gap-1"
v-if="!questionDetail?.isOfficialAccount"
>
{{ questionDetail.region }}
·
......@@ -332,8 +332,8 @@
</div>
<div class="text-[14px] text-slate-500 mb-3 flex items-center gap-3">
发布于 {{ dayjs(answer.createTime * 1000).format('YYYY-MM-DD HH:mm') }} ·
{{ answer.region }}
发布于 {{ dayjs(answer.createTime * 1000).format('YYYY-MM-DD HH:mm') }}
<span v-if="answer.region">·{{ answer.region }}</span>
</div>
<!-- 底 部吸附操作栏 -->
......
......@@ -82,7 +82,7 @@
<span class="flex items-center">
{{ formatNumber(videoDetail?.playCount) }}播放
</span>
<span v-if="!videoDetail?.isOfficialAccount" class="flex items-center">
<span v-if="videoDetail?.region" class="flex items-center">
·{{ videoDetail?.region }}
</span>
</div>
......
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