feat 临时

This commit is contained in:
2026-03-20 01:12:29 +08:00
parent a0baf2ad7c
commit 21965ef95e
14 changed files with 1186 additions and 98 deletions

59
backend/ai/ai_client.go Normal file
View File

@@ -0,0 +1,59 @@
package ai
import (
"fmt"
)
type Client struct {
enableSearch bool // 是否开启搜索
searchStrategy string // 搜索策略(开了搜索才生效)
temperature float32 // 采样温度
}
func NewAIClient(enableSearch bool, searchStrategy string, temperature float32) *Client {
return &Client{
enableSearch: enableSearch,
searchStrategy: searchStrategy,
temperature: temperature,
}
}
func (c *Client) RequestAI(pre string, prompt []string, cb func(content string, i int) error) error {
inToken, outToken := int32(0), int32(0)
messages := []Message{{
Role: "user",
Content: pre,
}}
for i, step := range prompt {
messages = append(messages, Message{
Role: "user",
Content: step,
})
r, err := request("qwen3-max", Parameters{
//r, err := request("deepseek-v3.2", Parameters{
EnableSearch: c.enableSearch,
SearchOptions: SearchOptions{
SearchStrategy: c.searchStrategy,
},
ResultFormat: "message",
Temperature: c.temperature,
}, messages)
if err != nil {
return err
}
inToken += r.Usage.InputTokens
outToken += r.Usage.OutputTokens
if len(r.Output.Choices) == 0 {
fmt.Printf("AI response err, r: %#+v\n", r)
return err
} else {
messages = append(messages, r.Output.Choices[0].Message)
}
fmt.Printf("AI 回答 %v: %v\n", i, r.Output.Choices[0].Message.Content)
if err = cb(r.Output.Choices[0].Message.Content, i); err != nil {
return err
}
}
fmt.Printf("AI finished, use token: in-%v out-%v\n", inToken, outToken)
return nil
}

82
backend/ai/ai_request.go Normal file
View File

@@ -0,0 +1,82 @@
package ai
import (
"bytes"
"encoding/json"
"io"
"net/http"
)
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
type Input struct {
Messages []Message `json:"messages"`
}
type Parameters struct {
EnableSearch bool `json:"enable_search,omitempty"`
SearchOptions SearchOptions `json:"search_options,omitempty"`
ResultFormat string `json:"result_format,omitempty"`
Temperature float32 `json:"temperature,omitempty"`
}
type SearchOptions struct {
SearchStrategy string `json:"search_strategy"`
}
type RequestBody struct {
Model string `json:"model"`
Input Input `json:"input"`
Parameters Parameters `json:"parameters"`
}
type ResponseBody struct {
Output struct {
Choices []struct {
Message Message `json:"message"`
FinishReason string `json:"finish_reason"`
} `json:"choices"`
} `json:"output"`
Usage struct {
InputTokens int32 `json:"input_tokens"`
OutputTokens int32 `json:"output_tokens"`
TotalTokens int32 `json:"total_tokens"`
} `json:"usage"`
}
func request(model string, parameters Parameters, messages []Message) (*ResponseBody, error) {
requestBody := RequestBody{
Model: model,
Input: Input{
Messages: messages,
},
Parameters: parameters,
}
jsonData, _ := json.Marshal(requestBody)
req, err := http.NewRequest("POST", "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation", bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+"sk-4daf94ad2fa94288b198d2a1d8924cef")
req.Header.Set("Content-Type", "application/json")
resp, err := (&http.Client{}).Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var res ResponseBody
if err = json.Unmarshal(body, &res); err != nil {
return nil, err
}
return &res, nil
}

View File

@@ -2,7 +2,11 @@ package backend
import ( import (
"context" "context"
"encoding/json"
"fiction/backend/ai"
"fmt" "fmt"
"os"
"path/filepath"
) )
// App struct // App struct
@@ -19,11 +23,67 @@ func (a *App) Startup(ctx context.Context) {
a.ctx = ctx a.ctx = ctx
} }
// Greet returns a greeting for the given name // GetNovelList returns the list of txt files in the source directory
func (a *App) Greet(name string) string { func (a *App) GetNovelList() []string {
return fmt.Sprintf("Hello %s, It's show time!", name) var novels []string
sourceDir := "./source"
files, err := os.ReadDir(sourceDir)
if err != nil {
fmt.Printf("Error reading source directory: %v\n", err)
return novels
}
for _, file := range files {
if !file.IsDir() && filepath.Ext(file.Name()) == ".txt" {
novels = append(novels, file.Name())
}
}
return novels
} }
func (a *App) Greet2(name string) string { // GetNovelContent returns the content of a txt file
return fmt.Sprintf("Hello %s, It's show time!", name) func (a *App) GetNovelContent(filename string) string {
sourceDir := "./source"
filePath := filepath.Join(sourceDir, filename)
content, err := os.ReadFile(filePath)
if err != nil {
fmt.Printf("Error reading file %s: %v\n", filename, err)
return "Error reading file"
}
return string(content)
}
func (a *App) WriteNovelContent(filename string) string {
filePath := filepath.Join("./config", "config.json")
content, err := os.ReadFile(filePath)
if err != nil {
fmt.Printf("读取提示词文件错误: %v\n", err)
return "Error reading file"
}
var str []string
if err = json.Unmarshal(content, &str); err != nil {
fmt.Printf("读取提示词错误: %v\n", err)
return "Error Unmarshal JSON"
}
resp := ""
err = ai.NewAIClient(false, "", 0.9).
RequestAI(
a.GetNovelContent(filename),
str,
func(content string, i int) error {
resp = content
return nil
},
)
if err != nil {
return err.Error()
}
return resp
} }

5
config/config.json Normal file
View File

@@ -0,0 +1,5 @@
[
"小说识别,识别出的仿写小说的信息,具体识别内容:\n(1)识别并记住原文的核心论点、支撑论据、风格特点及其独特的表达方式。(2)识别并记住原文的结构布局。",
"第2步 - 提供几个同类型的仿写主题,引导用户选择仿写的主题。本步骤完成后,询问用户提供信息是否 ” 满意,如果用户反馈 ” “ 满 意 ” 直接可以进入下一步,如果用户反馈 “ 不满意,则根据用户要求修改。",
"第3步,\n满意,仿写最推荐的主题。\n文章重塑 - 任务:根据新小说的主题,创造性地重新表达原文的核心观点和信息,避免直接抄袭。本步骤完成后,询问用户提供信息是否 ” 满意,如果用户反馈 ” ” 满意 ” 直接可以进入下一步,如果 用户反馈 “ 不满意,则根据用户要求修改。 ” \n要求:\n(1)仿写的小说符合抖音短篇小说风格:开篇炸点、简单粗暴、节奏快、情绪强等。\n(2)全文要有逻辑性、口语化、大白话、短句多、通俗易懂。\n(3)一句为一段。\n(4)模仿原小说60%的写作风格和语气、语法、拼写,同时确保内容的原创性和流畅性。 \n(5)仿写后的小说字数必须在1.2万字左右。(6)只输出小说内容,不要输出其他任何多余的话,内容不要写行号"
]

View File

@@ -8,6 +8,7 @@
"name": "frontend", "name": "frontend",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"element-plus": "^2.13.5",
"vue": "^3.2.37" "vue": "^3.2.37"
}, },
"devDependencies": { "devDependencies": {
@@ -61,6 +62,24 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@ctrl/tinycolor": {
"version": "4.2.0",
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz",
"integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/@element-plus/icons-vue": {
"version": "2.3.2",
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
"integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
"license": "MIT",
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/@esbuild/android-arm": { "node_modules/@esbuild/android-arm": {
"version": "0.15.18", "version": "0.15.18",
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.15.18.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.15.18.tgz",
@@ -95,12 +114,69 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@floating-ui/core": {
"version": "1.7.5",
"resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.5.tgz",
"integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.11"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.7.6",
"resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.6.tgz",
"integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.7.5",
"@floating-ui/utils": "^0.2.11"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.11",
"resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.11.tgz",
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
"license": "MIT"
},
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5", "version": "1.5.5",
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@popperjs/core": {
"name": "@sxzz/popperjs-es",
"version": "2.11.8",
"resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz",
"integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@types/lodash": {
"version": "4.17.24",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.24.tgz",
"integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==",
"license": "MIT"
},
"node_modules/@types/lodash-es": {
"version": "4.17.12",
"resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
"license": "MIT",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/web-bluetooth": {
"version": "0.0.20",
"resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
"integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==",
"license": "MIT"
},
"node_modules/@vitejs/plugin-vue": { "node_modules/@vitejs/plugin-vue": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-3.2.0.tgz", "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-3.2.0.tgz",
@@ -215,12 +291,85 @@
"integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==", "integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vueuse/core": {
"version": "12.0.0",
"resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-12.0.0.tgz",
"integrity": "sha512-C12RukhXiJCbx4MGhjmd/gH52TjJsc3G0E0kQj/kb19H3Nt6n1CA4DRWuTdWWcaFRdlTe0npWDS942mvacvNBw==",
"license": "MIT",
"dependencies": {
"@types/web-bluetooth": "^0.0.20",
"@vueuse/metadata": "12.0.0",
"@vueuse/shared": "12.0.0",
"vue": "^3.5.13"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/metadata": {
"version": "12.0.0",
"resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-12.0.0.tgz",
"integrity": "sha512-Yzimd1D3sjxTDOlF05HekU5aSGdKjxhuhRFHA7gDWLn57PRbBIh+SF5NmjhJ0WRgF3my7T8LBucyxdFJjIfRJQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared": {
"version": "12.0.0",
"resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-12.0.0.tgz",
"integrity": "sha512-3i6qtcq2PIio5i/vVYidkkcgvmTjCqrf26u+Fd4LhnbBmIT6FN8y6q/GJERp8lfcB9zVEfjdV0Br0443qZuJpw==",
"license": "MIT",
"dependencies": {
"vue": "^3.5.13"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/async-validator": {
"version": "4.2.5",
"resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
"license": "MIT"
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/dayjs": {
"version": "1.11.20",
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.20.tgz",
"integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
"license": "MIT"
},
"node_modules/element-plus": {
"version": "2.13.5",
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.13.5.tgz",
"integrity": "sha512-dmY24fhSREfZN/PuUt0YZigMso7wWzl+B5o+YKNN15kQIn/0hzamsPU+ebj9SES0IbUqsLX1wkrzYmzU8VrVOQ==",
"license": "MIT",
"dependencies": {
"@ctrl/tinycolor": "^4.2.0",
"@element-plus/icons-vue": "^2.3.2",
"@floating-ui/dom": "^1.0.1",
"@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
"@types/lodash": "^4.17.20",
"@types/lodash-es": "^4.17.12",
"@vueuse/core": "12.0.0",
"async-validator": "^4.2.5",
"dayjs": "^1.11.19",
"lodash": "^4.17.23",
"lodash-es": "^4.17.23",
"lodash-unified": "^1.0.3",
"memoize-one": "^6.0.0",
"normalize-wheel-es": "^1.2.0"
},
"peerDependencies": {
"vue": "^3.3.0"
}
},
"node_modules/entities": { "node_modules/entities": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz",
@@ -671,6 +820,29 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT"
},
"node_modules/lodash-es": {
"version": "4.17.23",
"resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.23.tgz",
"integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
"license": "MIT"
},
"node_modules/lodash-unified": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz",
"integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
"license": "MIT",
"peerDependencies": {
"@types/lodash-es": "*",
"lodash": "*",
"lodash-es": "*"
}
},
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.21", "version": "0.30.21",
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz",
@@ -680,6 +852,12 @@
"@jridgewell/sourcemap-codec": "^1.5.5" "@jridgewell/sourcemap-codec": "^1.5.5"
} }
}, },
"node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
"license": "MIT"
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
@@ -698,6 +876,12 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
} }
}, },
"node_modules/normalize-wheel-es": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
"license": "BSD-3-Clause"
},
"node_modules/path-parse": { "node_modules/path-parse": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",

View File

@@ -9,6 +9,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"element-plus": "^2.13.5",
"vue": "^3.2.37" "vue": "^3.2.37"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1 +1 @@
21d2a2199c4fb87865d8160b492f51c3 1ad03e99e099a0ecaf49d3493b03c367

View File

@@ -1,21 +1,200 @@
<script setup> <script setup>
import HelloWorld from './components/HelloWorld.vue'</script> import { ref, watch } from 'vue'
import Sidebar from './components/Sidebar.vue'
import { GetNovelContent, WriteNovelContent } from '../wailsjs/go/backend/App'
const activeButton = ref('')
const content = ref('请选择要查看的小说')
const currentTitle = ref('小说内容')
const viewMode = ref('original')
const currentFilename = ref('')
const isLoading = ref(false)
// 监听 viewMode 变化
watch(viewMode, (newMode) => {
if (newMode === 'imitation') {
content.value = currentFilename.value + ' 的仿写内容'
} else if (newMode === 'original' && currentFilename.value) {
// 切换回原文模式时,重新加载原文
handleMenuClick({ id: activeButton.value, filename: currentFilename.value })
}
})
async function handleMenuClick(menuData) {
if (menuData && menuData.id) {
activeButton.value = menuData.id
viewMode.value = 'original'
// 检查是否是小说文件
if (menuData.id.startsWith('novel') && menuData.filename) {
currentFilename.value = menuData.filename
currentTitle.value = `小说内容 - ${menuData.filename}`
content.value = '加载中...'
try {
const novelContent = await GetNovelContent(menuData.filename)
content.value = novelContent
} catch (error) {
console.error('Error loading novel content:', error)
content.value = '加载小说内容失败'
}
} else {
currentTitle.value = '小说内容'
content.value = '请选择要查看的小说'
}
}
}
async function handleImitation() {
if (currentFilename.value) {
isLoading.value = true
content.value = '正在生成仿写内容...'
try {
const imitationContent = await WriteNovelContent(currentFilename.value)
content.value = imitationContent
} catch (error) {
console.error('Error generating imitation content:', error)
content.value = '生成仿写内容失败'
} finally {
isLoading.value = false
}
} else {
content.value = '请先选择要仿写的小说'
}
}
</script>
<template> <template>
<img id="logo" alt="Wails logo" src="./assets/images/logo-universal.png"/> <div class="container">
<HelloWorld/> <Sidebar
v-model:activeButton="activeButton"
@menu-click="handleMenuClick"
/>
<div class="content">
<div class="content-header">
<h3>{{ currentTitle }}</h3>
<div class="header-buttons" v-if="activeButton">
<el-button
:type="viewMode === 'original' ? 'primary' : 'default'"
@click="viewMode = 'original'"
>
原文
</el-button>
<el-button
:type="viewMode === 'imitation' ? 'primary' : 'default'"
@click="viewMode = 'imitation'"
>
仿文
</el-button>
</div>
</div>
<div class="content-body">
<div v-if="viewMode === 'original'">
{{ content }}
</div>
<div v-else-if="viewMode === 'imitation'" class="imitation-container">
<div v-if="!isLoading && (content === '请选择要查看的小说' || content === '正在生成仿写内容...' || content === currentFilename + ' 的仿写内容')" class="imitation-button-container">
<el-button
type="primary"
size="large"
class="imitation-button"
@click="handleImitation"
:loading="isLoading"
:disabled="isLoading"
>
仿写
</el-button>
</div>
<div v-else class="imitation-content">
{{ content }}
</div>
</div>
<div v-else>
{{ content }}
</div>
</div>
</div>
</div>
</template> </template>
<style> <style>
#logo { * {
display: block; margin: 0;
width: 50%; padding: 0;
height: 50%; box-sizing: border-box;
margin: auto; }
padding: 10% 0 0;
background-position: center; body {
background-repeat: no-repeat; font-family: Arial, sans-serif;
background-size: 100% 100%; background-color: #1b2636;
background-origin: content-box; color: #fff;
}
.container {
display: flex;
height: 100vh;
overflow: hidden;
}
.content {
flex: 1;
display: flex;
flex-direction: column;
padding: 20px;
overflow-y: auto;
}
.content-header {
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.content-header h3 {
color: #fff;
font-size: 18px;
margin: 0;
}
.header-buttons {
display: flex;
gap: 10px;
}
.content-body {
flex: 1;
background-color: rgba(255, 255, 255, 0.05);
border-radius: 8px;
padding: 20px;
color: #ecf0f1;
min-height: 400px;
}
.imitation-container {
height: 100%;
padding: 20px;
}
.imitation-button-container {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.imitation-button {
padding: 15px 30px;
font-size: 18px;
}
.imitation-content {
height: 100%;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-word;
} }
</style> </style>

View File

@@ -1,71 +0,0 @@
<script setup>
import {reactive} from 'vue'
import {Greet} from '../../wailsjs/go/backend/App'
const data = reactive({
name: "",
resultText: "Please enter your name below 👇",
})
function greet() {
Greet(data.name).then(result => {
data.resultText = result
})
}
</script>
<template>
<main>
<div id="result" class="result">{{ data.resultText }}</div>
<div id="input" class="input-box">
<input id="name" v-model="data.name" autocomplete="off" class="input" type="text"/>
<button class="btn" @click="greet">Greet</button>
</div>
</main>
</template>
<style scoped>
.result {
height: 20px;
line-height: 20px;
margin: 1.5rem auto;
}
.input-box .btn {
width: 60px;
height: 30px;
line-height: 30px;
border-radius: 3px;
border: none;
margin: 0 0 0 20px;
padding: 0 8px;
cursor: pointer;
}
.input-box .btn:hover {
background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%);
color: #333333;
}
.input-box .input {
border: none;
border-radius: 3px;
outline: none;
height: 30px;
line-height: 30px;
padding: 0 10px;
background-color: rgba(240, 240, 240, 1);
-webkit-font-smoothing: antialiased;
}
.input-box .input:hover {
border: none;
background-color: rgba(255, 255, 255, 1);
}
.input-box .input:focus {
border: none;
background-color: rgba(255, 255, 255, 1);
}
</style>

View File

@@ -0,0 +1,114 @@
<script setup>
import { ref, onMounted } from 'vue'
import { Document } from '@element-plus/icons-vue'
import { GetNovelList } from '../../wailsjs/go/backend/App'
const props = defineProps({
activeButton: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:activeButton', 'menu-click'])
const novels = ref([])
function handleNovelClick(novelId, novelLabel) {
emit('update:activeButton', novelId)
emit('menu-click', { id: novelId, filename: novelLabel })
}
async function loadNovelList() {
try {
const novelList = await GetNovelList()
novels.value = novelList.map((novel, index) => ({
id: `novel${index + 1}`,
label: novel
}))
} catch (error) {
console.error('Error loading novel list:', error)
novels.value = []
}
}
onMounted(() => {
// 组件挂载时加载小说列表
loadNovelList()
})
</script>
<template>
<div class="sidebar">
<h2>小说列表</h2>
<div class="novel-list">
<div
v-for="novel in novels"
:key="novel.id"
class="novel-item"
:class="{ active: activeButton === novel.id }"
@click="handleNovelClick(novel.id, novel.label)"
>
<el-icon><Document /></el-icon>
<span>{{ novel.label }}</span>
</div>
<div v-if="novels.length === 0" class="empty-state">
暂无小说文件
</div>
</div>
</div>
</template>
<style scoped>
.sidebar {
width: 400px;
background-color: #2c3e50;
padding: 20px 0;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
height: 100vh;
overflow-y: auto;
}
.sidebar h2 {
text-align: center;
margin-bottom: 20px;
color: #fff;
font-size: 18px;
}
.novel-list {
padding: 0 10px;
}
.novel-item {
display: flex;
align-items: center;
padding: 12px 15px;
margin-bottom: 5px;
border-radius: 5px;
color: #ecf0f1;
cursor: pointer;
transition: background-color 0.3s;
}
.novel-item:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.novel-item.active {
background-color: #3498db;
color: #fff;
}
.novel-item .el-icon {
margin-right: 10px;
font-size: 16px;
}
.empty-state {
padding: 20px;
text-align: center;
color: #95a5a6;
font-style: italic;
}
</style>

View File

@@ -1,5 +1,12 @@
import {createApp} from 'vue' import {createApp} from 'vue'
import App from './App.vue' import App from './App.vue'
import './style.css'; import './style.css';
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
createApp(App).mount('#app') const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(ElementPlus).mount('#app')

View File

@@ -1,6 +1,8 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT // This file is automatically generated. DO NOT EDIT
export function Greet(arg1:string):Promise<string>; export function GetNovelContent(arg1:string):Promise<string>;
export function Greet2(arg1:string):Promise<string>; export function GetNovelList():Promise<Array<string>>;
export function WriteNovelContent(arg1:string):Promise<string>;

View File

@@ -2,10 +2,14 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT // This file is automatically generated. DO NOT EDIT
export function Greet(arg1) { export function GetNovelContent(arg1) {
return window['go']['backend']['App']['Greet'](arg1); return window['go']['backend']['App']['GetNovelContent'](arg1);
} }
export function Greet2(arg1) { export function GetNovelList() {
return window['go']['backend']['App']['Greet2'](arg1); return window['go']['backend']['App']['GetNovelList']();
}
export function WriteNovelContent(arg1) {
return window['go']['backend']['App']['WriteNovelContent'](arg1);
} }

View File

@@ -0,0 +1,462 @@
1
女儿高中家长会上,班主任提议将三年饭费一口气都存在饭卡。
女儿恰到好处的松了口气,小声的和我说:
“还好我爸给我存了一万,他说希望我吃好一点。”
我突然脸色一变,当着所有家长面站起来厉声道:
“退钱,把我女儿饭卡里的钱退给我!”
班主任为难道:“这都是无法退款的,而且是您先生自愿交的。”
女儿难堪的红了脸,扯了扯我的衣袖。
我咬紧牙不肯妥协,丈夫闻讯赶来,厉声道:
“你发什么疯!学校的营养餐那么贵,我想让女儿在学校吃得好一点,不行吗!”
我迎上众人的视线,一字一顿道:
“这笔钱不退,我们就离婚!”
......
“菲菲家长,我们学校的饭卡肯定是不能退的。”
班主任艰难开口,顾明宇也面色铁青:
“就因为我给女儿充饭卡?你就要和我离婚!她还是不是你女儿!”
女儿满脸涨红,崩溃道:
“妈我求你了,别在这里闹!”
同学们讥讽不可置信的目光不断的在我们身上扫视。
我不为所动,冰冷的视线落到女儿班主任身上:
“你们学校就这样专横?我们都要离婚了还不给我退钱?”
顾明宇拼命深呼吸,挂上僵硬的微笑:
“老婆,我知道你最近工作忙,心情不好,但你也不能在女儿学校撒野。”
“我给你买你上周最喜欢的包包好吗?”
我猛的伸手给顾明宇一耳光,用力到手拼命颤抖:
“别跟我转移话题,今天这笔钱你要是要不回来,我就和你离婚!”
女儿尖叫一声,挡在顾明宇面前,手掌抚上他红肿的脸。
她崩溃的哭道:
“妈!你到底要干什么!我要退学,我没脸在这儿念下去了!”
女儿的闺蜜纷纷上前安慰她,迫于教养不敢发声,就只用刀一样的眼神射向我。
其余家长却看不下去了。
“这是做什么,给自己女儿花钱还不乐意,难道是后妈?!”
“怎么能在学校动手呢,一点都不给孩子留脸啊!”
“就这样的人也配当妈妈?没听孩子爸说想要包吗?这是在这儿作呢!”
女儿啜泣着,冲上来拉住我的袖口。
望着她痛苦的神情,我的心里格外酸疼。
我把她从只有我小臂大小拉扯到如今亭亭玉立的模样。
当年我被医生断定生育困难,和顾明宇尝试无数次人工授精。
一次到上百次排卵针的痛苦终于让我拥有了她,所以我从不忍心让她受委屈。
我柔和了声音低声道:“顾菲,你和妈妈说实话,你爸给你饭卡充了多少钱?”
女儿急切道:“妈妈,就是一万块钱,我高中三年肯定不会乱花的,你不要再和爸爸闹了好不好?”
我的心骤然沉到了谷底,挣开了女儿的手,望着顾明宇再次强调:
“这笔钱你退还是不退!”
顾明宇冷哼一声:
“这笔钱是给女儿饭卡的,就算退了还要交,有什么必要!”
我深深地望了他一眼:“好。”
话音刚落,周围以为这场闹剧终于结束,纷纷松了口气。
我却猛的冲到了女儿书桌上抽出一张草稿纸手写了一份简易的离婚协议书,签好字递给顾明宇。
对上他错愕的双眼,我低声道:
“签字吧,既然你不想退钱,那我们就离婚。”
2
和我交好的家长周雯雯也忍不住过来拉扯我:
“言玉,你疯了是不是!顾先生对你多好啊!”
“你生完菲菲身体不好,他每天早晚都给你做营养餐;你出去上班,他不管工作多忙都接送你还照顾菲菲;离婚了你上哪儿找这么好的老公去!”
我依旧举着那份离婚协议书:
“顾明宇,签字。”
就在这时,女儿的脸色逐渐苍白,她小声呢喃了一句:“妈妈......”
下一秒猛的后仰,两眼一翻晕了过去。
女儿的闺蜜终于忍受不了,流着眼泪崩溃的冲我喊道:
“言阿姨!你难道没有心吗!菲菲十六岁,正是有自尊心的时候,你这么在学校闹,她要如何自处!”
她哽咽道:“菲菲最喜欢您了,下周是您的生日吧,她从上个月就开始准备给您的生日礼物了,您怎么能这么对她呢!”
望着昏迷的女儿,我心如刀绞,但我清楚的知道,她——不值得我同情!
顾明宇在第一时间冲了过去,死死掐住女儿的人中,直到看到女儿悠悠醒转,他才红着双眼后怕的把她抱进怀里。
女儿泪眼朦胧:
“爸爸,我没脸......我没脸在学校待下去了,我的世界崩塌了!”
顾明宇低声道:“没事的菲菲,有爸爸在。”
他转头对着班主任说:
“老师,麻烦您跟学校交涉一下,我们退一下饭卡里的钱,明天再交好吗?”
班主任也被这一场闹剧吓傻了,赶忙应下转身就走。
顾明宇这才冲我低声道:
“阿玉,这下你满意了吧,我已经和学校申请退款了。”
“你怀孕的时候说过的,我们一家三口永远不分开,所以这个离婚协议我不签!”
顾明宇拍了拍女儿,女儿挣扎着站起身,从自己书桌里掏出一个礼物盒。
“妈妈,这是我下周打算送给你的生日礼物。”
我颤抖着手看到了盒子里装的包包,和一封手写信。
手写信里没有华丽的语言,只有女儿对我的爱与感激。
我的手不停颤抖,眼泪也流了下来。
女儿哽咽道:
“妈妈,我把这半年的生活费都攒下来了但是还是不够你喜欢的那款包包,不过我给你买了这个差不多的,你满意吗?”
周围纷纷为女儿的孝心打动。
“多好的孩子啊!这女的就是作!把这么幸福的家庭整垮了就满意了!”
“我看她就是外面有人了,没看她女儿说攒了半年的钱也买不起她喜欢的包吗?捞女!这是傍上大款了!”
顾明宇闻言一脸生气:
“请你不要诋毁我的妻子,我妻子不是那样的人!”
面对顾明宇的维护,我没有丝毫的动容,反而是暖起来的心脏重归冰冷,让我恢复了理智。
“等退款到账,我们再说。”
就在这时,班主任急忙赶回来:
“我和财务处的老师交涉完了,已经同意退款了!顾先生快看看到账没有!”
顾明宇没有丝毫犹豫把退款到账的一万元连带着他账户上其余的钱都发给了我。
“阿玉,这些钱都给你。”
周围发出艳羡的声音。
顾明宇紧张的看着我:“这样你满意了吗?”
我看着转账信息:
“不!这钱不对!”
3
顾明宇急忙道:“是一万多,这个月你发给我的生活费我可都发回去了。”
有个男家长忍不住开口道:
“兄弟,要我说男人手里肯定得有钱,不然这女的就得踩到你头顶。”
他轻蔑的看我一眼,顾明宇面色苍白没力气再对我维护。
我不理他,一本正经的看向班主任:“不应该退给我一万,而是应该退我......”
“十万!”
班主任骤然脸色一变,周雯雯终于忍不住推了我一下:
“你是疯了吗,言玉!诈骗到学校来了!你还要不要脸了!”
此话一出瞬间惹发了众怒。
“这可是学校啊!到底还要不要脸!”
“我看这女的就是外面有人了,所以才着急要离婚!”
“对,瞧她说的,还要十万,这是财产没分割清楚呢!”
顾明宇彻底沉不住气了,他拽住我的手腕往外走:
“我们的事回家再处理,你不要在菲菲的学校丢人显眼!”
我嗤笑道:“怎么,怕你的小情人被我认出来!”
顾明宇怒了:“我有什么小情人,你到底还要胡说八道些什么!又说学校欠你十万块钱,又说我有小情人!”
我指着周雯雯道:
“她难道不是你的小情人吗?你对我做什么她都知道的一清二楚,对你了解的那么透彻!”
轰!全教室都炸开了锅,怀疑的视线落到了周雯雯身上。
她那张保养得当的脸瞬间涨得通红,声音颤抖:“言玉!你胡说八道些什么呢!”
因为过于急,她语无伦次道:
“咱俩孩子从小学就是同学,我们认识十多年了,我怎么可能不熟悉,你怎么能怀疑我呢!我一个单亲妈妈,你这样说,我不活了!”
她呜一边说一边瘫软到地上啜泣。
周雯雯的儿子周庭气的喘粗气,眼睛通红的朝我冲过来:
“你欺负顾菲还不够,现在又来欺负我妈!我打死你!”
话音刚落,顾明宇突然闪身挡到我面前。
周庭这一拳用了十足的力气,顾明宇本就红肿的侧脸更加雪上加霜,连血丝都渗透出来。
他却没生气,只是长叹了一口气:“叔叔替她给你道个歉。”
他转身,一脸无奈的看着我:
“既然你想离,那就离吧。”
他闭了闭眼睛:
“车子房子都归你,存款也归你,只有一点,女儿得归我。”
顾明宇这副遍体鳞伤却为爱妥协的形象感动了不少家长,有的感性一点甚至落下了泪。
偏偏我却不为所动。
“晚了!学校必须把这十万块钱退给我!”
这句话更是点燃了众人的怒火,辱骂声劈头盖脸的向我砸来。
这时,校长带着财务处主任走了进来。
财务处主任心疼的走到顾菲面前,正色对我说:
“顾菲妈妈,我理解孩子高中后,某些家长可能会出现压力过大精神混乱的症状,但你不该拿孩子的未来当儿戏啊!”
校长也面色铁青:“从没见过有家长能敲诈到学校来,既然如此,我们报警!”
“我行得正,坐得端,不怕人查!”
率先变了脸色的是顾明宇,他拼命鞠躬:
“对不起,是我妻子精神不好,对不起,别报警。”
他率先给我扣了精神病的帽子,周围纷纷露出了然的神情。
“怪不得呢,原来是个精神病啊!”
我对上校长怒气的眼神:“好!那就报警!”
“菲菲,别冲动!”
噼里啪啦的声音在身后响起,我转头,女儿正站在教室的窗口上。
她声泪俱下:“您一定要闹到人尽皆知吗!”
“如果您非要报警,我今天就从这里跳下去!”
4
我只觉得心像跌入冰窖一样冷。
“报警也只是为了解决问题,万一学校真的欠我十万怎么办!”
“万一你爸爸真的她小情人,甚至有了别的孩子,你又该怎么办!”
女儿尖叫道:
“不可能,我爸爸是世界上最好的爸爸,你不许污蔑他!”
她说着脚下一滑,整个人朝后仰去。
财务处主任猛的扑过去死死拉住了她的手。
我看见女儿下意识叫了句妈妈。
下一秒她挣扎起来:
“我已经没脸继续活了,你还不如让我去死!”
财务处主任急道:“孩子,活下去才有希望!孩子妈妈,你赶紧答应她吧!”
痛到极致的心脏反而让我格外清醒。
“不可能,今天就算她跳楼了,我也要报警!”
众人顾不上我的冷血,冲上去三下五除二把女儿拉了上来。
女儿仿佛看不见一样扑进顾明宇怀里嚎啕大哭。
“啪——”
剧烈的疼痛从我的右脸传来,我眼神冰冷没有丝毫犹豫一巴掌甩回去。
出乎所有人意料的动手的人是教导处主任。
她捂着脸哽咽道:
“我也是一位母亲,我不能忍受一个孩子接受这样的对待,你......”
我冷笑道:“跳楼的又不是你孩子,难道她是你女儿?!”
她目瞪口呆,众人也没想到我的无耻发言。
校长及时打圆场:
“陈老师的孩子前年出了车祸,成了植物人,这才做出过激举动,请你见谅。”
顾明宇情绪激动的在离婚协议书上签了字,猛的甩在我脸上:
“滚!我没有你这样的妻子,菲菲也没有你这样的妈妈!”
他低声道:“我们两个都是孤儿,好不容易组成了家庭,又好不容易有了女儿,你到底为什么要这样!”
他一副不理解的样子,我也想问,我也同样不理解。
警车的声音突然响彻在校园里,顾菲脸色苍白,她浑身颤抖:
“是谁报的警?”
周庭一脸怒火的瞪着我:
“我!我不允许任何人污蔑我妈!我要告她恶意诽谤!”
周雯雯一脸安慰的拉住了他。
这时警察走了进来,了解完事情经过后,有条理的准备调查财务处的明细以及账单。
校长脸色难看:
“如果查出来这件事情和我们学校无关,您女儿我们也教不下去了,请另择名校吧!”
这所学校是市内甚至省内的名校,女儿初中刻苦三年就为了考进这里。
闻此言,女儿跪在我面前声泪俱下:
“妈妈不查了好吗?我好不容易考上的,我初中每天五点起来背单词 每天晚上学到一点就为了考上这里啊!妈妈你是知道的!”
我当然知道,每天早上我都会更早起来为她准备丰盛的早餐,每天晚上我都会陪在她左右。
“你不是待不下去连死都不怕了吗?既然如此被开除也不是什么大事!”
我看着紧闭的房门的冷声道:
“回答不出我的问题,他就是死在里面我也不会开门。”
“当妈的怎么能说出来这种话,真是畜生啊!”
“摊上这样的妈,孩子真是惨!”
不知道哪里飞来的纸团砸到了我的头上。
接着更多利器从四面八方砸过来。
“砰——”
一个保温杯狠狠砸到我的额头上,鲜血汩汩流了下来。
所有人都加入了这场对我的审判,我狼狈的缩在角落护住脑袋。
“住手!”
就在这时,两名警察走了进来,神情严肃。
“经我们的调查,学校的确应该还给她十万元!”
5
班级里炸开了锅。
校长一副不可置信的表情,语气结巴:“警察…察…同志,您是不是搞错了,我......我不能......”
几名家长纷纷开口:
“学校居然真的收了人十万块钱,我天,这也太可怕了!”
“我还觉得这校长挺好一个人呢,居然也会贪!”
“没想到这位同学妈妈说的居然全是真的!”
“可是她当着所有家长的面前闹也太不给孩子面子了!”
我看着顾明宇,轻声叹了口气;“都到这个地步了,你还是不肯说实话吗?”
顾明宇垂在裤边的手紧紧攥着,恳求道:“阿玉,我们不要闹了好不好,我们回家吧。”
“我保证以后什么都听你的,你说什么是什,好吗?”
我摇头:“不好,顾明宇,我已经什么都知道了,你别想瞒着我了。”
话音刚落有人接道:“这话什么意思,难不成这笔钱是他觅下的?”
女儿眼泪汪汪:“我爸爸不是这种人,他就是当时多输了一个0,才给我饭卡里存了十万!”
我冷声道:“那你刚才为什么和我说是一万!”
她眼睛瞪起来:“只是一万块钱你就要这样闹,如果听到是十万,岂不是会把天都掀翻!”
“我没有你这样的妈妈!”
我浑身一颤,最后的一点心痛也随风消散。
“好,从今天开始,我也没有你这样的女儿!”
见事态发展越来越严重,校长连忙挡在我面前焦急道:
“言女士,我刚才亲自去查的,顾菲同学的饭卡里只有一万的流水,不存在十万块钱,我们学校绝对不可能贪孩子的钱!”
“你既然说你清楚真相,麻烦你还我一个清白!”
我把离婚协议收好,见我想亲自公布真相,警察给了我一个眼神退至门外。
“我现在不就说了?顾明宇有一个小情人。”
“你胡说!”
“我没有和周雯雯女士有任何不正当关系!”
我话音刚落,顾明宇父女便焦急的打断我。
财务处主任走到我面前苦口婆心:“言女士,现在一家平平安安的生活在一起不容易,即便顾先生有错,你也不敢......”
周围瞬间响起应和的声音:
“这位老师说的也对,哪个男人不犯错呢?”
“他这么顾家就是一次出错而已。”
“要我说,还是把这女的惯的!”
我突然冷笑着开口:“陈老师还真是大度,看来你老公也出轨多次咯。”
陈明月瞬间被噎住。
我继续开口道:“自从我女儿进入这所高中后,顾明宇就频繁往女儿的饭卡里打钱以转移我们的婚内财产。”
“最近的一笔,正是十万!”
周围瞬间一片哗然。
偏偏有人提出了疑惑声:“转移婚内财产怎么能拿饭卡呢,周庭妈妈可做不到从饭卡里转出钱来。”
本该怒目圆瞪我的周雯雯却突然笑出声。
她走到我身边掏出纸巾擦了擦我额头上的血迹。
“我可不是烂货的小情人。”
6
我的眼泪瞬间落下来,周雯雯心疼的把我抱进怀里。
“要不是我陪着言玉闹这场戏,怎么把场子热起来,让真正的小情人出场呢!”
众人议论纷纷。
“难不成小三就在在场的家长里?”
“指不定就是那个女的,你看她穿的就不像个正经人!”
“你放屁!你再说老娘撕了你的嘴!”
眼看在场人要打了起来,我突然笑出声了:“谁说小三是在场的家长了?”
我突然望向顾明宇:“能靠女儿的饭卡转移财产,也就是一定能接触到学校的。”
“我说的没错吧,财务处主任,陈明月老师?”
教室里迎来异常寂静,然后是翻天的惊呼。
“我靠......对啊,能转移到钱的肯定得是学校财务部的啊!”
“这到底是怎么发现的,也太厉害了!”
“身为一个老师居然做出这样的行为,真让人觉得恶心!”
陈明月额头渗出冷汗,她嘴巴嗫嚅却发不出声音。
校长大惊失色,下一秒勃然大怒:“陈明月,你居然能做出这么伤风败俗的事情!”
说起来,陈明月能毫无顾忌的转移校内资产不被发现,自然是因为她有一个完全信任她的校长爸爸!
顾明宇挡在陈明月面前,再也做不出对我的包容神情,他怨恨的看着我:
“你早就知道这回事,才在学校里闹事对不对?”
“就为了叫来警察抓住阿月的错处?言玉,我告诉你,你做梦!”
他猛的甩出一份协议书扔到桌子上。
那赫然是一份婚前财产分割书。
他痛心疾首道:“你自从生下菲菲之后一直情绪不稳定,我百般陪伴你,你却怎么都不满意。”
他凄然一笑:“所以当我遇到即便有植物人儿子却依旧坚强生活的阿月时,才会不受控制的被吸引。”
“我转给阿月的都是我的个人婚前财产,你曾说过婚前的东西我们都各自保留。”
我凄然一笑,满是识人不清而对自己的嘲讽。
“顾明宇,那份公证书是没用的。”
顾明宇顿时脸色一变。
我当年父母意外去世给我留下一笔巨额财产,亲戚建议我用隐瞒这件事,并用婚前财产来试探顾明宇。
但我最终还是没有把这份协议书送去公证,因为我觉得夫妻就是要相互信任。
校长一副备受打击的模样却还是出言为女儿狡辩:“言女士,我知道我的女儿破坏了你的家庭,可是她真的不是坏人!”
“她丈夫和人跑了,孩子又出了车祸变成植物人,明月她......只是太缺爱了。”
陈明月啜泣道:“言小姐,我会把钱还给你的,我和顾先生的不正当关系也会解除,实在抱歉我给您带来了不好的影响......”
父女俩声泪俱下的表演加上单身母亲坚强教坚强的人设瞬间吸引了在场少数人的同情。
有位女家长低声道:“要我说,这王八看绿豆看对眼了也不是什么大事,既然都打算离婚了,那就好聚好散呗。”
她上下打量着我讥讽道:“我觉得给孩子换个温柔妈挺好,省着亲妈没分寸,在学校闹让孩子下不来台!”
7
周雯雯大惊失色,打量着周围:“温柔妈?在哪儿呢,不会是陈小姐吧。”
她靠着我,笑出声。
这一笑把女家长笑毛了:“你笑什么!我没说错吗!陈老师这么温柔,还奋不顾身地救顾菲同学,而言玉又做了什么!女儿跳楼她都不松口!”
女儿终于抬起了头,她挡在了陈明月面前,对着我理所当然道:“虽然爸爸做错了,但是妈妈,陈老师毕竟救了我的命,你别怪她了。”
我看着这个我呵护长大的孩子,只觉得陌生。
周雯雯讥笑道:“可是我怎么听说她那个儿子在医院被丢到最差的病房,要不是医生护士呵护,怕是早就要断了气!”
想到医院里的一幕,有孩子的周雯雯更是红了眼睛:“那孩子满背的褥疮,瘦的不成人样!”
陈明月大惊失色,再维持不住表情狰狞道:“你撒谎!”
“我儿子在私立医院待的好好的,你怎么可能知道他什么情况!”
众人被两极分化的说法弄得一头雾水。
直到周庭和另一位男同学站了出来。
“因为陈厌是我们的初中同学!”
因为刚才那场戏,周庭还有些不好意思的冲我笑,但提到陈厌,他瞬间低落下去。
“初中的时候,陈厌就是我们班最瘦的人,多次因为营养不良和低血糖晕倒,校医要告诉家长,陈厌就特别惶恐!”
说到这里,周庭都有些心疼。
“陈厌说如果被妈妈知道,他肯定又要挨打!”
他指着陈明月说道:“那天我尾随陈厌回家,看到的他妈妈就是你!”
“我和丁明调查了三天才知道他车祸去的是哪家医院!这才发现了陈厌的惨状!”
即便知道这孩子已经转到最好的病房用上了最佳的治疗,我依旧心如刀绞。
“证据呢!没有证据你就不要胡说!”陈明月色厉茬苒。
周庭大步走向讲台,打开了教室的多媒体,他插上U盘。
陈明月狰狞的声音瞬间通过音响传遍全班:
“你这个小畜生命可真大,车祸居然都没要了你的命!”
“不过距离你死也快了,毕竟我不会找任何人给你治疗!”
“你就在这张小床上等死吧!”
全场家长都愣在了原地,刚才的女家长更是冲上去狠狠扇了顾明月一巴掌:
“贱人!你怎么能这么对待自己的儿子!”
一开头,众人纷纷上前动手,连警察都一时控制不了情况。
陈明月此时脸色苍白,百口莫辩,她仓皇的想要躲到顾明宇身后,却连带他也跟着挨打。
就在这时,我迈步走上讲台朗声道:
“警官,我要举报顾明宇和陈明月故意杀人!”
8
听到这句话,警察的脸色突的一变,眼神瞬间锐利起来。
“言女士,你说的是真的吗?要知道以现在的罪名,陈女士就有贪污和转移财产的罪名了!”
我知道他这是在暗示我明月会受到处罚,心里为人民警察点赞,却还是朗声道:“是!我有证据!”
但是她受到的处罚难解我心头之恨!
我掏出另一份U盘交给警方。
“这是陈厌出车祸当天损坏的监控,可以看到那辆车是车牌!”
我低声道:“这场车祸不了了之,是因为陈明月没有报警,也没有大肆宣扬。”
“那是因为撞了陈厌的正是我的前夫,顾明宇,是他们两个要害死这个孩子!”
全场哗然,校长更是浑身发抖,他从第一个视频就开始发呆。
直到现在他涨红了脸冲上去狠狠扇了陈明月一巴掌:“畜生啊!你!你!”
他声泪俱下:“你一直和我们说小厌不爱来外公外婆家,说他不懂事挑食才长不胖,总是和人打架才一身伤!”
“说是肇事司机已经伏法,说你妥善安置了小厌!”
想到因为女儿而讨厌外孙甚至没去看一眼的校长泪流满面。
他走到我面前深深鞠躬:“谢谢你言女士,今天,是我对不起你!”
陈明月尖声道:“爸!你就这么信了!谁知道她的证据是不是真的——你怎么能......”
陈明月的话音未落,突然一份录音响了起来。
“结婚快二十年了,言玉那个贱人还瞒着我,我才知道她父母给她留下一笔丰厚的财产!”
“等处理完这个野种,我们再杀了她,这笔钱就会归我,她的合法丈夫!”
随着我手里的录音笔播至尾声,顾明宇脸色也苍白如纸,连一句狡辩的话都说不出来。
警察厉声道:“扣上!”
这下家长们终于知道我为什么态度如此激烈:
“我的天哪,为了妻子的钱要杀了她!”
“现实版消失的她,这男的平常装的那么好,真是太恶心人了!”
警察将他们带走,却见临走前陈明月得意的看着我:“言玉,你别得意的太早!”
我笑着看低下头勾起嘴角的顾明宇:“我说了,我什么都知道哦顾明宇。”
顾明宇大惊失色,出了教学楼依旧能听到他的尖叫声:“不!不可能!”
校长仿佛老了十岁:“让各位家长见笑了,这次的家长会到这里结束了,散了吧。”
突然,一阵强烈的啜泣声响起。
顾菲哭到崩溃:“为什么会这样啊!为什么一场家长会,我爸妈离婚了不够还要把他们送进去啊!”
她哭的声泪俱下,一时引起一些感性的人的同情。
“要我说,还是孩子最惨,这妈也是够狠,想杀她又没杀成。”
“对,私下处理得了呗,这让孩子以后怎么做人。”
“走哪都有个杀人犯的爹,孩子以后难咯。”
我没有回应,周雯雯扶着我往门外走,她低声道:“还是去医院?”
我点点头正要开口,就听到顾菲可怜巴巴的:“妈妈你要去哪儿?”
“你也不要我了吗?”
9
我转头浅浅勾了下唇,看向顾菲的眼神再没有往日的疼爱。
“我不是你妈。”
这下全班家长又不满意了,开始指责我道:
“就算孩子爸做错了事,又和孩子有什么关系 你这种想法可不行!”
“对啊孩子是无辜的,因为你俩已经考不了公了,还丢了这么大的人。”
“好好对孩子,以后孩子要是过得不好,我家孩子告诉我了 我第一个报警告你虐待儿童!”
顾菲眼睛红的像兔子,我只是轻松的掏出两份报告递给校长:
“您看看吧。”
校长颤巍巍的把报告翻开,瞬间一副天打雷劈的样子。
他试图从我的表情里找出一抹玩笑,却发现,没有......
他低声开口:“顾菲,回来!”
顾菲第一次听校长如此严厉的声音,顿时眼泪流了下来,她向我寻求帮助撒娇道:“妈妈......”
周庭和周雯雯给我力量似的站在我一左一右,我对上顾菲的眼睛轻声道:
“陈明月才是你的亲生母亲。”
“他们两个也不是在顾菲上高中的时候勾搭起来的,而是初中同学,高中的初恋,大学的白月光。”
“我当年怀孕艰难,去人工授精才有了你,而那所医院的负责人正是你的爸爸,也就是顾明宇,巧的是,同一天将胚胎移入子宫的——”
“正是陈明月!”
全场惊呼,不可思议看向顾菲。
“你还真别说,这孩子脸上一点都找不到和言女士像的地方。”
“要是遮住上半张脸,倒是真挺像......陈明月的。”
顾菲嚎啕大哭起来,作势要冲进我怀里:
“妈妈,你不能不要我,我是你从小养到大的,我就是你的女儿!”
感受到众人因为顾菲的话态度上的迟疑,我冷笑道:“可是你早就知道了不是吗?”
“知道陈明月才是你的妈妈!”
所有人瞬间倒吸一口冷气。
顾菲还要狡辩,我继续说道:“刚才你作势要跳楼,陈明月拉住你的是个,你下意识的反应不会骗人。”
“你喊的是妈妈。”
我晃了晃手机:“如果你还想狡辩,我这手机里还有无数你们相认的证据 ”
见事情没有回转余地,顾菲一脸愤恨:“你早就知道,你是故意要抓我爸妈!”
其实,我是给过顾菲机会的。
毕竟是我一手带大的孩子,我宁愿相信她是一时走了歪路,也不愿想她从根子上就是坏的可能。
我转身离开了教室,校长拉住拼命挣扎,状似癫狂的顾菲。
“够了!再闹我就把你送回乡下老家!”
教室里的大家也纷纷散去,直到有人突然开口:
“要是这样的话,陈明月成植物人的儿子不就是言玉的亲生儿子!?”
10
我坐在VIP病房里,看着躺在病床上瘦弱的男孩,絮絮叨叨的说着家长会发生的事情。
“妈妈把坏人都惩罚好了,你什么时候才能起来看看妈妈呢。”
“我会努力做个好妈妈的,只希望你不要怪我错过了你这么多年。”
周雯雯不忍的把我抱进怀里:“言玉,你要振作!这孩子还得靠着你呢!”
陈厌其实和我一点都不像,但是周庭却总是在我和周雯雯耳边念叨着他有一个被虐待的同学。
那时我刚知道顾明宇有个白月光,这个儿子恰好就是白月光的儿子。
我去了医院见了这孩子一面,听到护士说:“唉,这孩子其实本来手术成功的几率很大,但居然是熊猫血,血库资源不足!”
她自以为小声的八卦道:“虽然亲生父子母子不能献血,但是居然和他爸妈血型都配不上!”
我起了疑心开始彻底调查顾明宇和陈明月才发现了这起完全针对我和我儿子的事故。
为了让他们身败名裂付出代价,我设计了这么一场大戏。
好在,结局是好的。
我捧起陈厌的手贴在额头上:“快醒来吧,妈妈等着你呢。”
一周后,陈明月和顾明宇的判决就下来了。
设涉及蓄意谋杀等一系列罪名,被判处死刑。
就在我走出病房的时候,有一个人影癫狂的冲向我,猛的跪在我面前:
“妈妈,求求你,求求你给爸爸他们一个谅解书吧,我不要他们死!”
顾菲一边哭一边疯了一样向我磕头,周围的目光瞬间聚集过来。
我看着她,仿佛在看一个陌生人。
“顾菲,他们差点害死我的儿子,又想要害死我。”
顾菲反驳道:“他不是没死还有恢复的可能吗?爸爸只是随口一说,你的一切都是给我的,他不会真的杀了你的!”
“而且就算你儿子死了,你还有我啊!”
我垂在裤边的手猛的抬起,狠狠地扇了顾菲一耳光。
“你再咒我儿子一句试试!”
顾菲捂着右脸不可置信的样子,这还是她出生开始我第一次打她。
对上我冰冷的眼神,顾菲强压下心底的不安。
正要开口,校长却一脸阴沉的站在她身后。
“打扰了,言小姐。”
我歪了歪头:“您也是来求我出具谅解书的?”
校长的脊背仿佛一夜之间弯了下去,头发花白。
因为女儿的杀人罪过,他迅速被革职了。
“不,我没脸!是我没教好这个女儿!她活该!”
他锐利的眼神射向顾菲:“我已经和你妈妈断绝了父女关系,我决不允许自己有一个杀人犯的女儿!”
“至于你,明天下午就回老家,我已经安排了人照顾到你成年,如果你考上大学了,我会承担你的学费和生活费,多余的就没有了!”
顾菲一脸惶恐,连忙跟上校长的步伐:“不!您不能这么对我!”
我转身走回病房,却僵愣在原地。
病床上躺着的少年,戴着呼吸面罩,轻轻的对我眨了眨眼睛。
陈厌恢复的很快,出院后的第一件事我带他去了一个地方。
“要做什么?”
他还是有些怕我,是陈明月这么多年的虐待让他对妈妈产生本能的恐惧。
“改名字。”
我朝他笑了笑:“不是讨厌的厌,从今天开始你随我的姓,是语言的言,言明生。”
“我希望明天永远是你的新生,永远蓬勃朝气。”
他眼眶通红,却不发一言。
......
我和周雯雯住在对门,每天早上,两家的门同时打开。
周庭火急火燎的穿上鞋,拉过安静的言明生朝电梯冲去:
“妈!言阿姨,拜拜,我们上学去了!”
周雯雯暴躁道:“臭小子,你慢点!”
我笑着挥手,看到言明生小心的摆了摆手。
风带着他的声音到我的耳边。
他说:
“妈妈,我上学去了。”
(完结)