Initial commit: One Pipe System

完整的管理系统,包含账户管理、卡片管理、套餐管理、财务管理等功能模块。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
sexygoat
2026-01-22 16:35:33 +08:00
commit 222e5bb11a
495 changed files with 145440 additions and 0 deletions

View File

@@ -0,0 +1,156 @@
<template>
<div class="comment-module">
<form @submit.prevent="addComment">
<div>
<input v-model="newComment.author" placeholder="你的名称" required />
<textarea v-model="newComment.content" placeholder="简单说两句..." required></textarea>
<button class="btn" type="submit">发布</button>
</div>
</form>
<ul>
<div class="comment-header">评论 {{ comments.length }}</div>
<CommentItem
class="comment-item"
v-for="comment in comments.slice().reverse()"
:key="comment.id"
:comment="comment"
:show-reply-form="showReplyForm"
@toggle-reply="toggleReply"
@add-reply="addReply"
/>
</ul>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import CommentItem from './widget/CommentItem.vue'
import { commentList, Comment } from '@/mock/temp/commentDetail'
const comments = commentList
const newComment = ref<Partial<Comment>>({
author: '',
content: ''
})
const showReplyForm = ref<number | null>(null)
const addComment = () => {
if (newComment.value.author && newComment.value.content) {
comments.value.push({
id: Date.now(),
author: newComment.value.author,
content: newComment.value.content,
timestamp: new Date().toISOString(),
replies: []
})
newComment.value.author = ''
newComment.value.content = ''
} else {
alert('请填写完整的评论信息')
}
}
const addReply = (commentId: number, replyAuthor: string, replyContent: string) => {
const comment = findComment(comments.value, commentId)
if (comment && replyAuthor && replyContent) {
comment.replies.push({
id: Date.now(),
author: replyAuthor,
content: replyContent,
timestamp: new Date().toISOString(),
replies: []
})
showReplyForm.value = null
} else {
alert('请填写完整的回复信息')
}
}
const toggleReply = (commentId: number) => {
showReplyForm.value = showReplyForm.value === commentId ? null : commentId
}
const findComment = (comments: Comment[], commentId: number): Comment | undefined => {
for (const comment of comments) {
if (comment.id === commentId) {
return comment
}
const found = findComment(comment.replies, commentId)
if (found) {
return found
}
}
return undefined
}
</script>
<style scoped lang="scss">
.comment-module {
.comment-header {
padding-bottom: 20px;
font-size: 18px;
font-weight: 500;
color: var(--art-gray-900);
}
.comment-item {
padding-bottom: 10px;
margin-bottom: 20px;
border-bottom: 1px solid var(--art-border-dashed-color);
}
form {
margin-bottom: 40px !important;
}
:deep(form) {
position: relative;
box-sizing: border-box;
width: 100%;
padding-bottom: 50px;
margin: auto;
> div {
input,
textarea {
box-sizing: border-box;
display: block;
width: 100%;
margin-top: 10px;
border: 1px solid var(--art-border-dashed-color);
outline: none;
}
input {
height: 36px;
padding-left: 10px;
}
textarea {
height: 100px;
padding: 10px;
}
.btn {
position: absolute;
right: 0;
bottom: 0;
display: inline-block;
width: 60px;
height: 32px;
margin-top: 15px;
font-size: 14px;
line-height: 30px;
color: #fff;
text-align: center;
cursor: pointer;
background-color: var(--main-color);
border: 0;
border-radius: 4px;
}
}
}
}
</style>

View File

@@ -0,0 +1,167 @@
<template>
<li class="comment-item">
<div class="comment-main">
<div class="comment-header">
<div class="avatar" :style="{ background: randomColor() }">{{
comment.author.substring(0, 1)
}}</div>
<strong class="name dark-text">{{ comment.author }}</strong>
</div>
<span class="content">{{ comment.content }}</span>
<div class="comment-info">
<span class="date">{{ formatDate(comment.timestamp) }}</span>
<div class="btn-text" @click="toggleReply(comment.id)">回复</div>
</div>
</div>
<ul class="comment-replies" v-if="comment.replies.length > 0">
<CommentItem
v-for="reply in comment.replies"
:key="reply.id"
:comment="reply"
:show-reply-form="showReplyForm"
@toggle-reply="toggleReply"
@add-reply="addReply"
/>
</ul>
<form v-if="showReplyForm === comment.id" @submit="handleSubmit">
<div>
<input v-model="replyAuthor" placeholder="你的名称" required />
<textarea v-model="replyContent" placeholder="你的回复" required></textarea>
<button class="btn" type="submit">发布</button>
</div>
</form>
</li>
</template>
<script setup lang="ts">
import AppConfig from '@/config'
import { ref } from 'vue'
interface Comment {
id: number
author: string
content: string
timestamp: string
replies: Comment[]
}
const props = defineProps<{
comment: Comment
showReplyForm: number | null
}>()
const emit = defineEmits<{
(event: 'toggle-reply', commentId: number): void
(event: 'add-reply', commentId: number, replyAuthor: string, replyContent: string): void
}>()
const replyAuthor = ref('')
const replyContent = ref('')
const toggleReply = (commentId: number) => {
emit('toggle-reply', commentId)
}
const addReply = (commentId: number, author: string, content: string) => {
emit('add-reply', commentId, author, content)
replyAuthor.value = ''
replyContent.value = ''
}
const handleSubmit = () => {
emit('add-reply', props.comment.id, replyAuthor.value, replyContent.value)
}
const formatDate = (timestamp: string) => {
const date = new Date(timestamp)
return date.toLocaleString()
}
let lastColor: string | null = null
const randomColor = () => {
let newColor: string
do {
const index = Math.floor(Math.random() * AppConfig.systemMainColor.length)
newColor = AppConfig.systemMainColor[index]
} while (newColor === lastColor)
lastColor = newColor
return newColor
}
</script>
<style scoped lang="scss">
.comment-module {
margin-top: 40px;
.comment-item,
.reply-item {
.comment-header {
display: flex;
align-items: center;
.avatar {
width: 30px;
height: 30px;
margin-right: 10px;
font-size: 12px;
font-weight: 500;
line-height: 30px;
color: #fff;
text-align: center;
background-color: var(--main-color);
border-radius: 50%;
}
.name {
display: block;
font-size: 14px;
font-weight: 500;
color: #000;
}
}
.content {
display: block;
margin-top: 10px;
font-size: 14px;
color: var(--art-gray-800);
}
.comment-info,
.reply-info {
display: flex;
align-items: center;
margin: 10px 0;
margin-top: 10px;
.date {
font-size: 12px;
color: var(--art-gray-500);
}
.btn-text {
margin-left: 20px;
font-size: 12px;
color: var(--art-gray-800);
cursor: pointer;
user-select: none;
&:hover {
color: var(--main-color);
}
}
}
}
.comment-replies {
padding-left: 10px;
.reply-item {
margin-top: 20px;
}
}
}
</style>