4 Commits
Author SHA1 Message Date
Vicky db313f4aa6 feat(admin): 新增商品配置与系统公告两个后台页面
- src/api/admin/product.ts、src/api/admin/notice.ts 对齐后端新接口
- ProductManagementView:列表/筛选/新建编辑/上下架/删除,新建默认下架有提示
- NoticeManagementView:列表/筛选/新建编辑/发布下架/删除
- 状态列一律用接口返回的 statusText,不在前端写死中文
- id 按字符串处理(雪花 id 超过 JS 安全整数范围,数字会被四舍五入)
2026-10-16 16:33:22 +08:00
Vicky 1148458ca1 chore(merge): 合入远端 main(全量作者标识)到 develop
为什么:远端 main 领先一个提交(1fd0b8b 全量补充作者标识),本地 main 领先一个(4564aa8
el-radio 修复);按条目要求先把远端合进来,再在 develop 上做 I1,main 本身不动(合并走 PR)。
2026-10-16 16:14:15 +08:00
Vicky 4564aa894d chore(web): 修掉 eslint 全量检查的 2 个 error,门禁口径改跑 eslint + pnpm build:pro
为什么:红线第 8 条要求 Web 门禁是 `pnpm build` + eslint 零错误,而此前只跑过 ts:check,
全量 eslint 存量有 2 个 error。Web 已冻结,本次只做"去噪"式修复,不改任何行为。

- BannerManagementView: 表格 slot 解构里去掉未被使用的 row($index 仍在用)
- CreatorCenterPage: 自闭合 <slot /> 改为 <slot></slot>(等价写法)
- 实测:eslint 0 error / 0 warning;pnpm build:pro = Build successful
2026-10-16 09:41:19 +08:00
Zhou Weidong 1fd0b8b100 style: 全量补充作者标识 // Author: fruit.com.cn 2026-10-16 00:02:03 +08:00
225 changed files with 1412 additions and 2 deletions

No files matched your search

+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
// @ts-check
const { defineConfig } = require('eslint-define-config')
module.exports = defineConfig({
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
module.exports = {
'*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'],
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': ['prettier --write--parser json'],
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
const modules = import.meta.glob('./**/*.mock.ts', {
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import { MockMethod } from 'vite-plugin-mock'
import { SUCCESS_CODE } from '@/constants'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import { SUCCESS_CODE } from '@/constants'
const timeout = 1000
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
module.exports = {
plugins: {
autoprefixer: {}
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
module.exports = {
printWidth: 100,
tabWidth: 2,
+2
View File
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { computed } from 'vue'
import { useAppStore } from '@/store/modules/app'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import request from '@/axios'
import type { AppUserBanRecord, AppUserItem, PageResult, WatchHistoryItem } from './types'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import request from '@/axios'
import type { BannerItem, BannerSaveData, PageResult } from './types'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import request from '@/axios'
import type { PageResult } from './types'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import request from '@/axios'
import type { ConfigItem } from './types'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import request from '@/axios'
import type { PageResult } from './types'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import request from '@/axios'
import type { PageResult } from './types'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import request from '@/axios'
/** 指标口径见后端 DashboardService 注释与 docs/api-phase1.md「2.8 数据看板」 */
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import request from '@/axios'
import type {
DramaCategory,
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import request from '@/axios'
import type { EpisodeItem, EpisodeSaveData } from './types'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import request from '@/axios'
import type { PageResult, CommentItem, ReportItem, SensitiveWordItem } from './types'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import request from '@/axios'
import type { OperationLogItem, PageResult } from './types'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import request from '@/axios'
import type { MediaAsset } from './types'
+66
View File
@@ -0,0 +1,66 @@
// Author: fruit.com.cn
import request from '@/axios'
import type { PageResult } from './types'
/** 系统公告:字段与 /admin/notices 的响应一一对应 */
export interface NoticeItem {
/** 雪花 id,后端以字符串下发(超过 JS 安全整数范围),原样回传即可 */
id: string
title: string
content: string
/** ALL / USER / CREATOR */
target: string
targetText: string
/** SYSTEM / AUDIT / PAYMENT / ACTIVITY */
type: string
typeText: string
/** 1 已发布 / 0 已下架 */
status: number
statusText: string
publishAt?: string
expireAt?: string
createdAt?: string
}
export interface NoticePageParams {
pageIndex?: number
pageSize?: number
status?: number
target?: string
type?: string
keyword?: string
}
export interface NoticeSaveData {
title: string
content: string
target: string
type: string
publishAt?: string | null
expireAt?: string | null
}
export const pageNoticesApi = (params: NoticePageParams) => {
return request.get<PageResult<NoticeItem>>({ url: '/admin/notices/page', params })
}
export const getNoticeApi = (id: string | number) => {
return request.get<NoticeItem>({ url: `/admin/notices/${id}` })
}
export const createNoticeApi = (data: NoticeSaveData) => {
return request.post<string>({ url: '/admin/notices', data })
}
export const updateNoticeApi = (id: string | number, data: NoticeSaveData) => {
return request.put<void>({ url: `/admin/notices/${id}`, data })
}
export const updateNoticeStatusApi = (id: string | number, status: number) => {
return request.put<void>({ url: `/admin/notices/${id}/status`, params: { status } })
}
export const deleteNoticeApi = (id: string | number) => {
return request.delete<void>({ url: `/admin/notices/${id}` })
}
+85
View File
@@ -0,0 +1,85 @@
// Author: fruit.com.cn
import request from '@/axios'
import type { PageResult } from './types'
/** 商品(金币包):字段与 /admin/products 的响应一一对应 */
export interface ProductItem {
/** 雪花 id,后端以字符串下发(超过 JS 安全整数范围),原样回传即可 */
id: string
code: string
name: string
/** COIN_PACK / VIP */
type: string
typeText: string
/** ALL / GOOGLE / APPLE */
platform: string
storeProductId: string
coinAmount: number
bonusCoin: number
/** coinAmount + bonusCoin,服务端算好,前端不再自己加 */
totalCoin: number
priceAmount: number
currency: string
displayPrice: string
regionCodes: string
vipDays: number
sort: number
/** 1 已上架 / 0 已下架 */
status: number
statusText: string
effectiveAt?: string
expireAt?: string
createdAt?: string
}
export interface ProductPageParams {
pageIndex?: number
pageSize?: number
type?: string
status?: number
platform?: string
keyword?: string
}
export interface ProductSaveData {
code: string
name: string
type: string
platform: string
storeProductId: string
coinAmount: number
bonusCoin: number
priceAmount: number
currency: string
displayPrice: string
regionCodes: string
vipDays: number
sort: number
effectiveAt?: string | null
expireAt?: string | null
}
export const pageProductsApi = (params: ProductPageParams) => {
return request.get<PageResult<ProductItem>>({ url: '/admin/products/page', params })
}
export const getProductApi = (id: string | number) => {
return request.get<ProductItem>({ url: `/admin/products/${id}` })
}
export const createProductApi = (data: ProductSaveData) => {
return request.post<string>({ url: '/admin/products', data })
}
export const updateProductApi = (id: string | number, data: ProductSaveData) => {
return request.put<void>({ url: `/admin/products/${id}`, data })
}
export const updateProductStatusApi = (id: string | number, status: number) => {
return request.put<void>({ url: `/admin/products/${id}/status`, params: { status } })
}
export const deleteProductApi = (id: string | number) => {
return request.delete<void>({ url: `/admin/products/${id}` })
}
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import request from '@/axios'
import type {
MenuNode,
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
export interface PageResult<T> {
total: number
list: T[]
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import request from '@/axios'
import type { AdminUserItem, AdminUserSaveData, PageResult } from './types'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import request from '@/axios'
import type { UserType } from './types'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
export type UserLoginType = {
username: string
password: string
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import { AxiosResponse, InternalAxiosRequestConfig } from './types'
import { ElMessage } from 'element-plus'
import qs from 'qs'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import service from './service'
import { CONTENT_TYPE } from '@/constants'
import { useUserStoreWithOut } from '@/store/modules/user'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import axios, { AxiosError } from 'axios'
import { defaultRequestInterceptors, defaultResponseInterceptors } from './config'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import type {
InternalAxiosRequestConfig,
AxiosResponse,
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import Backtop from './src/Backtop.vue'
export { Backtop }
+2
View File
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { ElBacktop } from 'element-plus'
import { useDesign } from '@/hooks/web/useDesign'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import Breadcrumb from './src/Breadcrumb.vue'
export { Breadcrumb }
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script lang="tsx">
import { ElBreadcrumb, ElBreadcrumbItem } from 'element-plus'
import { ref, watch, computed, unref, defineComponent, TransitionGroup } from 'vue'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import { pathResolve } from '@/utils/routerHelper'
import type { RouteMeta } from 'vue-router'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import BaseButton from './src/Button.vue'
export { BaseButton }
+2
View File
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { useDesign } from '@/hooks/web/useDesign'
import { ElButton, ComponentSize, ButtonType } from 'element-plus'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import Collapse from './src/Collapse.vue'
export { Collapse }
+2
View File
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { computed, unref } from 'vue'
import { useAppStore } from '@/store/modules/app'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import ConfigGlobal from './src/ConfigGlobal.vue'
export type { ConfigGlobalTypes } from './src/types'
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { provide, computed, watch, onMounted } from 'vue'
import { propTypes } from '@/utils/propTypes'
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import { ComponentSize } from 'element-plus'
export interface ConfigGlobalTypes {
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import ContentDetailWrap from './src/ContentDetailWrap.vue'
export { ContentDetailWrap }
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { ElCard } from 'element-plus'
import { propTypes } from '@/utils/propTypes'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import ContentWrap from './src/ContentWrap.vue'
export { ContentWrap }
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { ElCard, ElTooltip } from 'element-plus'
import { propTypes } from '@/utils/propTypes'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import ContextMenu from './src/ContextMenu.vue'
import { ElDropdown } from 'element-plus'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
import { PropType, ref } from 'vue'
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
export interface ContextMenuSchema {
disabled?: boolean
divided?: boolean
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import Descriptions from './src/Descriptions.vue'
export type { DescriptionsSchema } from './src/types'
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script lang="tsx">
import { ElCollapseTransition, ElDescriptions, ElDescriptionsItem, ElTooltip } from 'element-plus'
import { useDesign } from '@/hooks/web/useDesign'
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
export interface DescriptionsSchema {
span?: number // 占多少分
field: string // 字段名
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import Dialog from './src/Dialog.vue'
export { Dialog }
+2
View File
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { ElDialog, ElScrollbar } from 'element-plus'
import { propTypes } from '@/utils/propTypes'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import Editor from './src/Editor.vue'
import { IDomEditor } from '@wangeditor/editor'
+2
View File
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { onBeforeUnmount, computed, PropType, unref, nextTick, ref, watch, shallowRef } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import Error from './src/Error.vue'
export { Error }
+2
View File
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import pageError from '@/assets/svgs/404.svg'
import networkError from '@/assets/svgs/500.svg'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import Form from './src/Form.vue'
import type { FormSchema, FormSetProps } from './src/types'
export type {
+2
View File
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script lang="tsx">
import { PropType, defineComponent, ref, computed, unref, watch, onMounted } from 'vue'
import {
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import type { Component } from 'vue'
import {
ElCascader,
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import { useI18n } from '@/hooks/web/useI18n'
import { PlaceholderModel, FormSchema, ComponentNameEnum, ColProps } from '../types'
import { isFunction } from '@/utils/is'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import {
AutocompleteProps,
InputNumberProps,
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import Icon from './src/Icon.vue'
export type { IconTypes } from './src/types'
+2
View File
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { computed, unref } from 'vue'
import { ElIcon } from 'element-plus'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
export interface IconTypes {
size?: number
color?: string
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import InputPassword from './src/InputPassword.vue'
export { InputPassword }
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { ref, unref, computed, watch } from 'vue'
import { ElInput } from 'element-plus'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import LocaleDropdown from './src/LocaleDropdown.vue'
export type { Language, LocaleDropdownType } from './src/types'
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { computed, unref } from 'vue'
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
export interface Language {
el: Recordable
name: string
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import Logo from './src/Logo.vue'
export { Logo }
+2
View File
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { ref, watch, computed, onMounted, unref } from 'vue'
import { useAppStore } from '@/store/modules/app'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import Menu from './src/Menu.vue'
export { Menu }
+2
View File
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script lang="tsx">
import { computed, defineComponent, unref, PropType } from 'vue'
import { ElMenu, ElScrollbar } from 'element-plus'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import { ref, unref } from 'vue'
import { findPath } from '@/utils/tree'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import Screenfull from './src/Screenfull.vue'
export { Screenfull }
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { Icon } from '@/components/Icon'
import { useFullscreen } from '@vueuse/core'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import { FormSchema, FormSetProps } from '../Form'
import Search from './src/Search.vue'
+2
View File
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="tsx">
import { Form, FormSchema, FormSetProps } from '@/components/Form'
import { PropType, computed, unref, ref, watch, onMounted } from 'vue'
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { ElButton } from 'element-plus'
import { useIcon } from '@/hooks/web/useIcon'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import { FormSchema } from '@/components/Form'
export interface SearchProps {
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import Setting from './src/Setting.vue'
export { Setting }
+2
View File
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { ElDrawer, ElDivider, ElMessage } from 'element-plus'
import { ref, unref } from 'vue'
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { PropType, watch, unref, ref } from 'vue'
import { propTypes } from '@/utils/propTypes'
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { ElSwitch, ElMessage } from 'element-plus'
import { useI18n } from '@/hooks/web/useI18n'
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { useAppStore } from '@/store/modules/app'
import { computed } from 'vue'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import SizeDropdown from './src/SizeDropdown.vue'
export { SizeDropdown }
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { computed } from 'vue'
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ComponentSize } from 'element-plus'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import TabMenu from './src/TabMenu.vue'
export { TabMenu }
+2
View File
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script lang="tsx">
import { usePermissionStore } from '@/store/modules/permission'
import { useAppStore } from '@/store/modules/app'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import { getAllParentPath } from '@/components/Menu/src/helper'
import type { RouteMeta } from 'vue-router'
import { isUrl } from '@/utils/is'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import Table from './src/Table.vue'
import { ElTable } from 'element-plus'
import { TableColumn, TableSetProps } from './src/types'
+2
View File
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script lang="tsx">
import {
ElTable,
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import {
ElDrawer,
@@ -1,3 +1,5 @@
<!-- Author: fruit.com.cn -->
<script lang="tsx">
import { defineComponent, unref, computed, PropType, ref } from 'vue'
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ComponentSize } from 'element-plus'
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
export const setIndex = (reserveIndex: boolean, index: number, size: number, current: number) => {
const newIndex = index + 1
if (reserveIndex) {
+2
View File
@@ -1,3 +1,5 @@
// Author: fruit.com.cn
import { TableProps as ElTableProps } from 'element-plus'
export interface TableColumn {
field: string
Loaded 100 of 225 files, more files were not shown because too many files have changed in this diff. Show more