frontend code

This commit is contained in:
Natalie Wu 2025-01-24 12:02:34 -05:00
commit e64d36c402
93 changed files with 30427 additions and 0 deletions

12
.babelrc Executable file
View File

@ -0,0 +1,12 @@
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
"plugins": ["transform-vue-jsx", "transform-runtime"]
}

9
.editorconfig Executable file
View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

14
.gitignore vendored Executable file
View File

@ -0,0 +1,14 @@
.DS_Store
node_modules/
/dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln

10
.postcssrc.js Executable file
View File

@ -0,0 +1,10 @@
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
"plugins": {
"postcss-import": {},
"postcss-url": {},
// to edit target browsers: use "browserslist" field in package.json
"autoprefixer": {}
}
}

154
AdminLogin.vue Normal file
View File

@ -0,0 +1,154 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>员工登录 - OOIN智能知识库</title>
<style>
body {
margin: 0;
padding: 0;
background-color: #1a1a1a;
color: white;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
margin-bottom: 20px;
}
.logo img {
height: 40px;
}
.login-container {
width: 100%;
max-width: 400px;
padding: 20px;
}
.login-title {
text-align: center;
margin-bottom: 10px;
font-size: 24px;
font-weight: 500;
}
.login-subtitle {
text-align: center;
color: #888;
margin-bottom: 30px;
font-size: 14px;
}
.login-form {
background-color: rgba(255, 255, 255, 0.1);
padding: 30px;
border-radius: 8px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #888;
}
.form-group input {
width: 100%;
padding: 12px;
border: none;
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.1);
color: white;
box-sizing: border-box;
}
.form-group input::placeholder {
color: #666;
}
.remember-me {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.remember-me input {
margin-right: 8px;
}
.forgot-password {
float: right;
color: #ef4444;
text-decoration: none;
font-size: 14px;
}
.login-button {
width: 100%;
padding: 12px;
background-color: #ef4444;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.support-text {
text-align: center;
margin-top: 20px;
font-size: 14px;
color: #666;
}
.support-link {
color: #ef4444;
text-decoration: none;
}
</style>
</head>
<body>
<div class="logo">
<img src="admin-pages/images/Logo copy.svg" alt="OOIN Logo">
</div>
<div class="login-container">
<h1 class="login-title">员工登录</h1>
<div class="login-subtitle">登录到OOIN数据库系统</div>
<form class="login-form">
<div class="form-group">
<label>邮箱地址</label>
<input type="email" placeholder="请输入邮箱地址">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" placeholder="请输入密码">
</div>
<div class="remember-me">
<input type="checkbox" id="remember">
<label for="remember">记住我</label>
<a href="#" class="forgot-password">忘记密码</a>
</div>
<button type="submit" class="login-button">登录</button>
</form>
<div class="support-text">
遇到问题<a href="#" class="support-link">联系技术支持</a>
</div>
</div>
</body>
</html>

21
README.md Executable file
View File

@ -0,0 +1,21 @@
# competitive_analysis_system
> A Vue.js project
## Build Setup
``` bash
# install dependencies
npm install
# serve with hot reload at localhost:8080
npm run dev
# build for production with minification
npm run build
# build for production and view the bundle analyzer report
npm run build --report
```
For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).

41
build/build.js Executable file
View File

@ -0,0 +1,41 @@
'use strict'
require('./check-versions')()
process.env.NODE_ENV = 'production'
const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('../config')
const webpackConfig = require('./webpack.prod.conf')
const spinner = ora('building for production...')
spinner.start()
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
webpack(webpackConfig, (err, stats) => {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
chunks: false,
chunkModules: false
}) + '\n\n')
if (stats.hasErrors()) {
console.log(chalk.red(' Build failed with errors.\n'))
process.exit(1)
}
console.log(chalk.cyan(' Build complete.\n'))
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
))
})
})

54
build/check-versions.js Executable file
View File

@ -0,0 +1,54 @@
'use strict'
const chalk = require('chalk')
const semver = require('semver')
const packageConfig = require('../package.json')
const shell = require('shelljs')
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
const versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
}
]
if (shell.which('npm')) {
versionRequirements.push({
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
})
}
module.exports = function () {
const warnings = []
for (let i = 0; i < versionRequirements.length; i++) {
const mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (let i = 0; i < warnings.length; i++) {
const warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}

BIN
build/logo.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

101
build/utils.js Executable file
View File

@ -0,0 +1,101 @@
'use strict'
const path = require('path')
const config = require('../config')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const packageConfig = require('../package.json')
exports.assetsPath = function (_path) {
const assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders = function (options) {
options = options || {}
const cssLoader = {
loader: 'css-loader',
options: {
sourceMap: options.sourceMap
}
}
const postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: options.sourceMap
}
}
// generate loader string to be used with extract text plugin
function generateLoaders (loader, loaderOptions) {
const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
publicPath:'../../',
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
const output = []
const loaders = exports.cssLoaders(options)
for (const extension in loaders) {
const loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
exports.createNotifierCallback = () => {
const notifier = require('node-notifier')
return (severity, errors) => {
if (severity !== 'error') return
const error = errors[0]
const filename = error.file && error.file.split('!').pop()
notifier.notify({
title: packageConfig.name,
message: severity + ': ' + error.name,
subtitle: filename || '',
})
}
}

22
build/vue-loader.conf.js Executable file
View File

@ -0,0 +1,22 @@
'use strict'
const utils = require('./utils')
const config = require('../config')
const isProduction = process.env.NODE_ENV === 'production'
const sourceMapEnabled = isProduction
? config.build.productionSourceMap
: config.dev.cssSourceMap
module.exports = {
loaders: utils.cssLoaders({
sourceMap: sourceMapEnabled,
extract: isProduction
}),
cssSourceMap: sourceMapEnabled,
cacheBusting: config.dev.cacheBusting,
transformToRequire: {
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: 'xlink:href'
}
}

82
build/webpack.base.conf.js Executable file
View File

@ -0,0 +1,82 @@
'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
context: path.resolve(__dirname, '../'),
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
},
node: {
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
}

95
build/webpack.dev.conf.js Executable file
View File

@ -0,0 +1,95 @@
'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')
const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)
const devWebpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
},
// cheap-module-eval-source-map is faster for development
devtool: config.dev.devtool,
// these devServer options should be customized in /config/index.js
devServer: {
clientLogLevel: 'warning',
historyApiFallback: {
rewrites: [
{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
],
},
hot: true,
contentBase: false, // since we use CopyWebpackPlugin.
compress: true,
host: HOST || config.dev.host,
port: PORT || config.dev.port,
open: config.dev.autoOpenBrowser,
overlay: config.dev.errorOverlay
? { warnings: false, errors: true }
: false,
publicPath: config.dev.assetsPublicPath,
proxy: config.dev.proxyTable,
quiet: true, // necessary for FriendlyErrorsPlugin
watchOptions: {
poll: config.dev.poll,
}
},
plugins: [
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
module.exports = new Promise((resolve, reject) => {
portfinder.basePort = process.env.PORT || config.dev.port
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
// publish the new Port, necessary for e2e tests
process.env.PORT = port
// add port to devServer config
devWebpackConfig.devServer.port = port
// Add FriendlyErrorsPlugin
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
},
onErrors: config.dev.notifyOnErrors
? utils.createNotifierCallback()
: undefined
}))
resolve(devWebpackConfig)
}
})
})

145
build/webpack.prod.conf.js Executable file
View File

@ -0,0 +1,145 @@
'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const env = require('../config/prod.env')
const webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true,
usePostCSS: true
})
},
devtool: config.build.productionSourceMap ? config.build.devtool : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false
}
},
sourceMap: config.build.productionSourceMap,
parallel: true
}),
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),
// Setting the following option to `false` will not extract CSS from codesplit chunks.
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
allChunks: true,
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
cssProcessorOptions: config.build.productionSourceMap
? { safe: true, map: { inline: false } }
: { safe: true }
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// keep module.id stable when vendor modules does not change
new webpack.HashedModuleIdsPlugin(),
// enable scope hoisting
new webpack.optimize.ModuleConcatenationPlugin(),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks (module) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}),
// This instance extracts shared chunks from code splitted chunks and bundles them
// in a separate chunk, similar to the vendor chunk
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
new webpack.optimize.CommonsChunkPlugin({
name: 'app',
async: 'vendor-async',
children: true,
minChunks: 3
}),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
if (config.build.productionGzip) {
const CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig

14
config/dev.env.js Executable file
View File

@ -0,0 +1,14 @@
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
API: '"/devApi"',
CHAT_SIMILRITY: '"0.3"',
NODE_CHAT_TITLE:'"OOIN小助手"',
NODE_LOGO:'"OOIN智能知识库"',
NODE_TITLE:'"智能知识库管理"',
BASE_URL: '"/"',
MODEL_ID: '"eb803ca4-69b2-11ef-a5a6-fa163e378f38"',
})

83
config/index.js Executable file
View File

@ -0,0 +1,83 @@
'use strict'
// Template version: 1.3.1
// see http://vuejs-templates.github.io/webpack for documentation.
const path = require('path')
module.exports = {
dev: {
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {
'/devApi': {
// target: 'http://yx.yanxi:8080',
target: 'http://180.163.88.62:30226',
ws: false,
changeOrigin: true,
pathRewrite: {
'^/devApi': ''
}
},
},
// Various Dev Server settings
host: 'localhost', // can be overwritten by process.env.HOST
port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
autoOpenBrowser: false,
errorOverlay: true,
notifyOnErrors: true,
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
/**
* Source Maps
*/
// https://webpack.js.org/configuration/devtool/#development
devtool: 'cheap-module-eval-source-map',
// If you have problems debugging vue-files in devtools,
// set this to false - it *may* help
// https://vue-loader.vuejs.org/en/options.html#cachebusting
cacheBusting: true,
cssSourceMap: true
},
build: {
// Template for index.html
index: path.resolve(__dirname, '../dist/index.html'),
// Paths
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
// assetsPublicPath: './',
assetsPublicPath: '/rag/',
/**
* Source Maps
*/
productionSourceMap: true,
// https://webpack.js.org/configuration/devtool/#production
devtool: '#source-map',
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css','img'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
}
}
}

11
config/prod.env.js Executable file
View File

@ -0,0 +1,11 @@
'use strict'
module.exports = {
NODE_ENV: '"production"',
API: '"/yxapi"',
CHAT_SIMILRITY: '"0.3"',
BASE_URL: '"/rag/"',
NODE_CHAT_TITLE:'"言晞小助手"',
NODE_LOGO:'"OOIN智能知识库"',
NODE_TITLE:'"智能知识库管理"',
MODEL_ID: '"eb803ca4-69b2-11ef-a5a6-fa163e378f38"',
}

12
index.html Executable file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>OOIN小助手</title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

15504
package-lock.json generated Executable file

File diff suppressed because it is too large Load Diff

73
package.json Executable file
View File

@ -0,0 +1,73 @@
{
"name": "competitive_analysis_system",
"version": "1.0.0",
"description": "A Vue.js project",
"author": "cycxs508@sina.com <724982540@qq.com>",
"private": true,
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"build": "node build/build.js"
},
"dependencies": {
"@microsoft/fetch-event-source": "^2.0.1",
"ant-design-vue": "^1.7.8",
"axios": "^0.18.0",
"highlight.js": "9.18.1",
"marked": "0.8.0",
"md5": "^2.3.0",
"moment": "^2.30.1",
"vue": "^2.5.2",
"vue-infinite-scroll": "^2.0.2",
"vue-router": "^3.0.1",
"vuex": "^3.6.2"
},
"devDependencies": {
"autoprefixer": "^7.1.2",
"babel-core": "^6.22.1",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-loader": "^7.1.1",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-plugin-transform-vue-jsx": "^3.5.0",
"babel-preset-env": "^1.3.2",
"babel-preset-stage-2": "^6.22.0",
"chalk": "^2.0.1",
"copy-webpack-plugin": "^4.0.1",
"css-loader": "^0.28.0",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^1.1.4",
"friendly-errors-webpack-plugin": "^1.6.1",
"html-webpack-plugin": "^2.30.1",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"node-notifier": "^5.1.2",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"ora": "^1.2.0",
"portfinder": "^1.0.13",
"postcss-import": "^11.0.0",
"postcss-loader": "^2.0.8",
"postcss-url": "^7.2.1",
"rimraf": "^2.6.0",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"uglifyjs-webpack-plugin": "^1.1.1",
"url-loader": "^0.5.8",
"vue-loader": "^13.3.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.5.2",
"webpack": "^3.6.0",
"webpack-bundle-analyzer": "^2.9.0",
"webpack-dev-server": "^2.9.1",
"webpack-merge": "^4.1.0"
},
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}

37
src/App.vue Executable file
View File

@ -0,0 +1,37 @@
<template>
<a-config-provider :locale="zh_CN">
<div id="app">
<router-view/>
</div>
</a-config-provider>
</template>
<script>
import zh_CN from "ant-design-vue/lib/locale-provider/zh_CN";
export default {
name: 'App',
data() {
return {
zh_CN
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
height: 100%;
min-height: 600px;
}
html, body {
padding: 0 !important;
margin: 0 !important;
height: 100%;
}
</style>

42
src/api/index.js Executable file
View File

@ -0,0 +1,42 @@
import {deleteAction, deleteAction1, getAction, postAction} from "./manage";
// 知识库列表
export const listPage = (params) => getAction(`/api/dataset/${params.pageNum}/${params.pageSize}`);
// 新增知识库
export const addDataset = (params) => postAction(`/api/dataset`, params);
// 删除知识库
export const deleteDataset = (params) => deleteAction(`/api/dataset`, params);
// 知识库详情
export const documentDetail = (documentId) => getAction(`/api/dataset/${documentId}/document`);
// 知识库文件详情
export const documentFileDetail = (params) => getAction(`/api/dataset/${params.documentId}/document/${params.fileId}/paragraph`);
// 新增知识库文件
export const addDocument = (params) => postAction(`/api/dataset/${params.id}/document`, params);
// 删除知识库文件
export const documentFileDelete = (params) => deleteAction1(`/api/dataset/${params.documentId}/document/${params.fileId}`, params);
// 创建会话id
export const chatOpen = (params) => postAction(`/api/application/chat_workflow/open`, params);
// 获取会话记录
export const getChatRecord = (params) => getAction(`/api/application/f3acba96-0504-11ef-9c5a-0242ac110006/chat`, params);
// 根据会话id获取具体的对话记录
export const getChatRecordByChatId = (params) => getAction(`/api/application/f3acba96-0504-11ef-9c5a-0242ac110006/chat/${params.chatId}/chat_record/`);
// 根据文档ID获取参考段落信息
export const getChatDatasetQuote = (params) => getAction(`/api/dataset/${params.datasetId}/hit_test`,params);
//文心大模型
export const wenxinModel = (params) => postAction(`/wenxinApi/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro`, params);
export default {
listPage,
getChatRecord,
getChatRecordByChatId,
addDataset,
documentDetail,
documentFileDetail,
documentFileDelete,
deleteDataset,
addDocument,
chatOpen,
getChatDatasetQuote,
wenxinModel,
}

209
src/api/manage.js Executable file
View File

@ -0,0 +1,209 @@
import Vue from 'vue'
import { axios } from '@/utils/request'
axios.defaults.timeout = 60000;
//postFormData
export function postFormDataAction(url, formData) {
return axios({
url: url,
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
//post
export function postAction(url, parameter) {
return axios({
url: url,
method: 'post',
data: parameter,
})
}
//put
export function putAction(url, parameter) {
return axios({
url: url,
method: 'put',
data: parameter
})
}
//patch
export function patchAction(url, parameter) {
return axios({
url: `${url}/${parameter.id}`,
method: 'patch',
data: parameter
})
}
//get
export function getAction(url, parameter) {
return axios({
url: url,
method: 'get',
params: parameter
})
}
export function deleteAction1(url) {
return axios({
url: `${url}`,
method: 'delete'
})
}
//deleteAction
export function deleteAction(url, { id }) {
return axios({
url: `${url}/${id}`,
method: 'delete'
})
}
export function delPostAction(url, {id}) {
return axios({
url: `${url}/${id}`,
method: 'post',
})
}
export function getUserList(parameter) {
return axios({
url: api.user,
method: 'get',
params: parameter
})
}
export function getRoleList(parameter) {
return axios({
url: api.role,
method: 'get',
params: parameter
})
}
export function getServiceList(parameter) {
return axios({
url: api.service,
method: 'get',
params: parameter
})
}
export function getPermissions(parameter) {
return axios({
url: api.permissionNoPager,
method: 'get',
params: parameter
})
}
// id == 0 add post
// id != 0 update put
export function saveService(parameter) {
return axios({
url: api.service,
method: parameter.id == 0 ? 'post' : 'put',
data: parameter
})
}
/**
* 下载文件 用于excel导出
* @param url
* @param parameter
* @returns {*}
*/
export function downFile(url, parameter, method = 'get') {
if (method == 'get') {
return axios({
url: url,
params: parameter,
method: method,
responseType: 'blob'
})
} else {
return axios({
url: url,
method: method,
data: parameter,
responseType: 'blob'
})
}
}
/**
* 下载文件
* @param url 文件路径
* @param fileName 文件名
* @param parameter 请求方式
* @returns {*}
*/
export function downloadFile(url, fileName, parameter) {
return downFile(url, parameter).then((data) => {
if (!data || data.size === 0) {
Vue.prototype['$message'].warning('文件下载失败')
return
}
if (typeof window.navigator.msSaveBlob !== 'undefined') {
window.navigator.msSaveBlob(new Blob([data]), fileName)
} else {
let url = window.URL.createObjectURL(new Blob([data]))
let link = document.createElement('a')
link.style.display = 'none'
link.href = url
link.setAttribute('download', fileName)
document.body.appendChild(link)
link.click()
document.body.removeChild(link) //下载完成移除元素
window.URL.revokeObjectURL(url) //释放掉blob对象
}
})
}
/**
* 文件上传 用于富文本上传图片
* @param url
* @param parameter
* @returns {*}
*/
export function uploadAction(url, parameter) {
return axios({
url: url,
data: parameter,
method: 'post',
headers: {
'Content-Type': 'multipart/form-data' // 文件上传
}
})
}
/**
* 获取文件服务访问路径
* @param avatar
* @param subStr
* @returns {*}
*/
export function getFileAccessHttpUrl(avatar, subStr) {
if (!subStr) subStr = 'http'
try {
if (avatar && avatar.startsWith(subStr)) {
return avatar
} else {
if (avatar && avatar.length > 0 && avatar.indexOf('[') == -1) {
// return window._CONFIG['staticDomainURL'] + "/" + avatar;
return avatar
}
}
} catch (err) {
return
}
}

BIN
src/assets/Slice 13@2x.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
src/assets/avatar.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
src/assets/bg-footer.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
src/assets/bg.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
src/assets/btn-bg.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
src/assets/cai-active.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

BIN
src/assets/cai.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

BIN
src/assets/chat-bg.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
src/assets/chat-tip.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
src/assets/close-active.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
src/assets/close.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

View File

@ -0,0 +1,35 @@
<svg width="50" height="17" viewBox="0 0 50 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="7.47419" cy="9.44087" rx="7.47419" ry="7.47431" fill="#F1F1E9"/>
<mask id="mask0_2606_2955" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="1" width="15" height="16">
<ellipse cx="7.47419" cy="9.44111" rx="7.47419" ry="7.47431" fill="#FEFEFE"/>
</mask>
<g mask="url(#mask0_2606_2955)">
<ellipse cx="11.3355" cy="9.31656" rx="4.35994" ry="4.36002" fill="#FF2C3F"/>
</g>
<mask id="mask1_2606_2955" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="1" width="15" height="16">
<ellipse cx="7.47419" cy="9.44111" rx="7.47419" ry="7.47431" fill="#FEFEFE"/>
</mask>
<g mask="url(#mask1_2606_2955)">
<ellipse cx="11.3355" cy="9.31656" rx="4.35994" ry="4.36002" fill="#FF2C3F"/>
</g>
<ellipse cx="22.4224" cy="9.44111" rx="7.47419" ry="7.47431" fill="#F1F1E9"/>
<mask id="mask2_2606_2955" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="14" y="1" width="16" height="16">
<ellipse cx="22.4224" cy="9.44111" rx="7.47419" ry="7.47431" fill="#FEFEFE"/>
</mask>
<g mask="url(#mask2_2606_2955)">
<ellipse cx="26.2838" cy="9.31631" rx="4.35994" ry="4.36002" fill="#FF2C3F"/>
</g>
<mask id="mask3_2606_2955" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="14" y="1" width="16" height="16">
<ellipse cx="22.4224" cy="9.44111" rx="7.47419" ry="7.47431" fill="#FEFEFE"/>
</mask>
<g mask="url(#mask3_2606_2955)">
<ellipse cx="26.2838" cy="9.31631" rx="4.35994" ry="4.36002" fill="#FF2C3F"/>
</g>
<path d="M34.614 2.87305V16.0302H31.1914V2.87305H34.614Z" fill="#F1F1E9"/>
<path d="M40.2713 2.87354L46.5413 10.9184H46.5758V2.87354H49.9999V16.0307H46.5758L40.3072 7.96859H40.2713V16.0307H36.8486V2.87354H40.2713Z" fill="#F1F1E9"/>
<path d="M44.4758 0.000488281V1.67298H44.2673V0.812306C44.2673 0.707057 44.2676 0.599653 44.2687 0.490452C44.2694 0.380534 44.2709 0.280314 44.273 0.190152C44.245 0.280314 44.213 0.380893 44.1767 0.491889C44.14 0.602167 44.104 0.708134 44.0688 0.809432L43.7726 1.67298H43.5108L43.2103 0.805121C43.1758 0.704542 43.1405 0.600371 43.1053 0.491889C43.0697 0.382689 43.0381 0.283547 43.0104 0.194462C43.014 0.284624 43.0161 0.384126 43.0161 0.493326C43.0161 0.602526 43.0161 0.708853 43.0161 0.812306V1.67298H42.8076V0.000488281H43.1513L43.646 1.49624L44.1393 0.000488281H44.4758Z" fill="#F1F1E9"/>
<path d="M46.0232 1.50294V1.67249H44.9102V0H45.9988V0.169548H45.1187V0.75147H45.9269V0.921018H45.1187V1.50294H46.0232Z" fill="#F1F1E9"/>
<path d="M47.0559 0.0100579C47.1853 0.0265816 47.2997 0.0668132 47.3996 0.130753C47.4992 0.193974 47.5772 0.282699 47.634 0.396569C47.6905 0.510798 47.7189 0.650532 47.7189 0.816128C47.7189 0.982083 47.6894 1.12505 47.6311 1.24574C47.5736 1.36572 47.4934 1.46091 47.391 1.53168C47.2892 1.6028 47.1738 1.64627 47.0444 1.66243C47.002 1.66746 46.9671 1.67033 46.9394 1.67105C46.9125 1.67213 46.8765 1.67249 46.8316 1.67249H46.334V0H46.8431C46.888 0 46.9243 0.000718376 46.9524 0.00143684C46.98 0.0025145 47.0146 0.00538819 47.0559 0.0100579ZM47.0228 1.49001C47.1645 1.4681 47.2777 1.39949 47.3622 1.28454C47.4474 1.16959 47.4902 1.01369 47.4902 0.816128C47.4902 0.630416 47.4499 0.483858 47.3694 0.376453C47.2889 0.269408 47.1771 0.204032 47.0343 0.181042C47.0084 0.176373 46.9786 0.173499 46.9452 0.172421C46.9125 0.170625 46.8747 0.169548 46.8316 0.169548H46.5425V1.50294H46.8186C46.8625 1.50294 46.9009 1.50258 46.9337 1.5015C46.9671 1.49971 46.9969 1.49576 47.0228 1.49001Z" fill="#F1F1E9"/>
<path d="M48.2642 1.67249H48.0557V0H48.2642V1.67249Z" fill="#F1F1E9"/>
<path d="M49.5598 1.08051H48.9443L48.7473 1.67249H48.5244L49.1255 0H49.4074L49.9841 1.67249H49.7597L49.5598 1.08051ZM49.0019 0.91096H49.5037L49.2621 0.165237L49.0019 0.91096Z" fill="#F1F1E9"/>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
src/assets/con-bg.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
src/assets/dateset-active.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

BIN
src/assets/dateset.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 B

BIN
src/assets/default.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
src/assets/document.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
src/assets/excel.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

BIN
src/assets/full-screen-active.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 B

BIN
src/assets/full-screen.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
src/assets/head-bg.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

BIN
src/assets/icon-knowledge.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
src/assets/logo-icon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/assets/logo.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
src/assets/logout.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

BIN
src/assets/main-head-bg.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
src/assets/message-sent.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/assets/msg-bg.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
src/assets/no-data-img.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
src/assets/no-data-list-btn.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/assets/no-data-logo.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
src/assets/no-key.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

BIN
src/assets/ooin-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
src/assets/pdf.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

BIN
src/assets/ppt.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

BIN
src/assets/red-robot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
src/assets/robot.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
src/assets/send.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
src/assets/settings-active.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/assets/settings.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/assets/sidebar-active.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
src/assets/sidebar.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
src/assets/system.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

BIN
src/assets/user.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
src/assets/word.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 B

BIN
src/assets/zan-active.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

BIN
src/assets/zan.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

View File

@ -0,0 +1,91 @@
<template>
<a-modal :width="685" centered :visible="visible" title="命中片段" :confirmLoading="confirmLoading"
@cancel="handleCancel" class="form-modal">
<ul class="menu">
<li v-for="(item,index) in quoteList" :key="index" class="menu-li">
{{ item.content }}
<p class="similarity">匹配度{{ Math.floor(item.similarity * 100) / 100 }}</p>
</li>
</ul>
<template slot="footer">
<a-button @click="handleCancel"> </a-button>
</template>
</a-modal>
</template>
<script>
export default {
data(){
return{
visible: false,
confirmLoading: false,
model: {},
quoteList:[],
}
},
props:{
datasetQuoteList:{
type: Array,
required: true
}
},
methods:{
async edit(record) {
this.model = Object.assign({}, record)
this.visible = true
this.handleList()
},
async handleList(){
this.quoteList = this.datasetQuoteList.filter(acc=>acc.document_name == this.model.document_name)
},
handleCancel(){
this.visible = false
},
}
}
</script>
<style lang="less" scoped>
div[aria-hidden="true"] {
display: none !important;
}
.menu{
list-style: none;
padding: 0;
position: relative;
height: 480px;
overflow: hidden;
overflow-y: scroll;
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 5px;
}
.menu-li{
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 14px;
color: #333333;
text-align: left;
margin-bottom: 20px;
border-bottom: 1px dashed rgba(4,24,73,0.15);
.similarity{
margin-top: 10px;
font-size: 14px;
color: #347ae2;
margin-bottom: 14px;
text-align: right;
}
&:last-child{
border-bottom: none;
}
}
}
</style>

View File

@ -0,0 +1,182 @@
<template>
<div class="login-page">
<div class="login-container">
<div class="logo">
<img src="../../assets/company-logo.svg" alt="OOIN Logo" class="logo-img">
</div>
<h1 class="login-title">管理员登录</h1>
<div class="login-subtitle">登录到OOIN数据库系统</div>
<form class="login-form" @submit.prevent="handleLogin">
<div class="form-group">
<label>邮箱地址</label>
<input type="email" v-model="email" placeholder="请输入邮箱地址">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" v-model="password" placeholder="请输入密码">
</div>
<div class="remember-me">
<input type="checkbox" id="remember" v-model="rememberMe">
<label for="remember">记住我</label>
<a href="#" class="forgot-password" @click.prevent="handleForgotPassword">忘记密码?</a>
</div>
<button type="submit" class="login-button">登录</button>
</form>
<div class="support-text">
遇到问题<a href="#" class="support-link" @click.prevent="contactSupport">联系技术支持</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'AdminLogin',
data() {
return {
email: '',
password: '',
rememberMe: false
}
},
methods: {
handleLogin() {
console.log('Login attempt', {
email: this.email,
password: this.password,
rememberMe: this.rememberMe
});
},
handleForgotPassword() {
console.log('Forgot password clicked');
},
contactSupport() {
console.log('Contact support clicked');
}
}
}
</script>
<style lang="less" scoped>
.login-page {
min-height: 100vh;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: #000000;
padding: 20px;
box-sizing: border-box;
}
.login-container {
width: 100%;
max-width: 400px;
text-align: center;
.logo {
margin-bottom: 40px;
.logo-img {
width: 110px;
}
}
.login-title {
color: white;
font-size: 24px;
font-weight: 500;
margin: 0 0 10px;
}
.login-subtitle {
color: #888;
font-size: 14px;
margin-bottom: 40px;
}
.login-form {
background-color: #18181b;
padding: 30px;
border-radius: 8px;
text-align: left;
.form-group {
margin-bottom: 20px;
label {
display: block;
color: #888;
margin-bottom: 8px;
}
input {
width: 100%;
padding: 12px;
background-color: #27272a;
border: none;
border-radius: 4px;
color: white;
box-sizing: border-box;
&::placeholder {
color: #666;
}
}
}
.remember-me {
display: flex;
align-items: center;
margin-bottom: 20px;
color: #888;
input {
margin-right: 8px;
}
label {
margin-right: auto;
}
}
.forgot-password {
color: #ef4444;
text-decoration: none;
}
.login-button {
width: 100%;
padding: 12px;
background-color: #ef4444;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin-top: 20px;
&:hover {
background-color: darken(#ef4444, 5%);
}
}
}
.support-text {
margin-top: 20px;
font-size: 14px;
color: #666;
.support-link {
color: #ef4444;
text-decoration: none;
}
}
}
</style>

View File

@ -0,0 +1,331 @@
<template>
<div class="main">
<div class="main-head">
<!-- <span class="main-head-title"> {{ logo }} OOIN智能知识库</span> -->
<div class="main-head-title">
<img src="../../assets/company-logo.svg" alt="COIN Logo" class="header-logo">
<span>OOIN智能知识库</span>
</div>
<!-- <img src="../../assets/logo-icon.png" alt="" class="logo"> -->
<div class="right">
<!-- <a-icon type="search" class="icon-ball" />
<a-icon type="bell" class="icon-ball" /> -->
<img src="../../assets/avatar.png" alt="" class="icon-user">
<a-dropdown>
<a class="ant-dropdown-link" @click="e => e.preventDefault()">
{{ userName }}
<!-- Marci Fumons -->
<!-- <a-icon type="down" /> -->
</a>
<a-menu slot="overlay">
<a-menu-item>
<a href="javascript:;" @click="handleLogout">退出登陆</a>
</a-menu-item>
</a-menu>
<!--<a-menu slot="overlay">
<a-menu-item>
<a href="javascript:;">退出登陆</a>
</a-menu-item>
<a-menu-item>
<a href="javascript:;">消息</a>
</a-menu-item>
</a-menu> -->
</a-dropdown>
</div>
</div>
<div class="main-con">
<div class="main-left">
<div class="main-menu">
<a-menu style="width: 100%" :default-selected-keys="['1']" :default-open-keys="['sub1']"
mode="inline" :selected-keys="[current]" @click="handleClick">
<a-menu-item key="1" @click="changeMenu('list')">
<div class="icon icon-profile" :class="current == '1' ? 'active' : ''"></div>
<!-- <a-icon type="profile" /> -->
知识库
</a-menu-item>
<a-menu-item key="2" @click="changeMenu('setting')">
<div class="icon icon-setting" :class="current == '2' ? 'active' : ''"></div>
设置
</a-menu-item>
</a-menu>
</div>
<!-- <div class="main-left-footer">
<img src="../../assets/logout.png" class="logout"/>
<p class="logout-title">退出</p>
</div> -->
</div>
<div class="main-con-right">
<router-view />
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
current: '1',
logo: process.env.NODE_LOGO,
userName: '',
userRole: '',
userToken: ''
}
},
created() {
// Get user info from localStorage
this.userName = localStorage.getItem('userName') || '';
this.userRole = localStorage.getItem('userRole') || '';
this.userToken = localStorage.getItem('userToken') || '';
// If no user info, redirect to login
if (!this.userToken) {
this.$router.push('/login');
}
},
methods: {
changeMenu(route) {
if (this.$route.path !== route) {
this.$router.push(route)
}
},
handleClick(e) {
console.log('click ', e);
this.current = e.key;
},
handleLogout() {
// Clear user info from localStorage
localStorage.removeItem('userToken');
localStorage.removeItem('userName');
localStorage.removeItem('userRole');
// Redirect to login page
this.$router.push('/login');
}
},
}
</script>
<!-- <script>
export default {
data() {
return {
current: '1',
logo:process.env.NODE_LOGO
}
},
created() {
},
methods: {
//
changeMenu(route) {
if (this.$route.path !== route) {
//
this.$router.push(route)
}
},
handleClick(e) {
console.log('click ', e);
this.current = e.key;
},
},
}
</script> -->
<style lang="less" scoped>
.main {
background-color: #000000; //
height: 100%;
width: 100%;
.main-head {
display: flex;
justify-content: space-between;
border-bottom: 1px solid #27272a; //
padding: 0 50px;
box-sizing: border-box;
height: 60px;
background-color: #18181b; //header
.main-head-title{
display: flex;
align-items: center;
font-size: 32px;
font-weight: 600;
color: #ef4444; //
line-height: 60px;
.header-logo {
width: 110px;
height: 110px;
margin-right: 12px;
}
}
.logo {
// background: url('../../assets/logo-icon.png') no-repeat center center;
// background-size: 100% 100%;
width: 110px;
height: 34px;
margin-top: 13px;
}
.right {
margin-top: 14px;
.icon-ball {
font-size: 16px;
margin-right: 28px;
}
.icon-user {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 12px;
}
}
}
.main-con {
display: flex;
flex: 1;
// flex-direction: column;
height: 100%;
height: calc(100% - 60px);
background-color: #18181b; //
.main-left {
height: 100%;
width: 283px;
display: flex;
border-right: 1px solid #27272a; //
flex-direction: column;
background-color: #18181b; //
//add new styles
/deep/ .ant-menu {
background-color: #18181b !important;
}
/deep/ .ant-menu-item {
color: #a1a1aa !important;
&:hover {
color: #ffffff !important;
background-color: #27272a !important;
}
&.ant-menu-item-selected {
background-color: #27272a !important;
color: #ef4444 !important;
}
}
// /deep/ .ant-menu-item {
// color: #a1a1aa !important;
// background-color: #27272a !important;
// }
.main-menu {
flex: 1;
padding-top: 52px;
width: 283px;
text-align: left;
.ant-menu-inline .ant-menu-item {
width: 100%;
border: none !important;
font-family: Poppins, Poppins;
font-weight: 500;
font-size: 16px;
color: #a1a1aa; //
line-height: 24px;
height: 24px;
display: flex;
align-items: center;
padding-left: 58px !important;
background: transparent; //
margin-bottom: 14px;
.anticon {
font-size: 16px !important;
}
.icon {
width: 20px;
height: 20px;
margin-right: 12px;
cursor: pointer;
}
.icon-profile {
background: url('../../assets/dateset.png') no-repeat center center;
background-size: 100% 100%;
&.active,
&:hover {
background: url('../../assets/dateset-active.png') no-repeat center center;
background-size: 100% 100%;
}
}
.icon-setting {
background: url('../../assets/settings.png') no-repeat center center;
background-size: 100% 100%;
&.active,
&:hover {
background: url('../../assets/settings-active.png') no-repeat center center;
background-size: 100% 100%;
}
}
}
.ant-menu-item-active {
color: #ef4444 !important; //
}
.ant-menu-item-selected {
color: #ef4444 !important; //
height: 24px !important;
border-right: #27272a !important; //
}
}
.main-left-footer {
height: 40px;
display: flex;
align-items: center;
margin:0 auto 40px;
cursor: pointer;
.logout{
width: 24px;
height: 24px;
margin-right: 8px;
}
.logout-title{
font-family: Poppins, Poppins;
font-weight: 500;
font-size: 16px;
color: #CE4F51; //changed color
line-height: 24px;
text-align: left;
font-style: normal;
margin:0
}
}
}
.main-con-right {
flex: 1;
padding: 0 56px 0 30px;
background-color: #000000; //
}
}
}
</style>

View File

@ -0,0 +1,230 @@
<template>
<div class="login-page">
<div class="login-container">
<div class="logo">
<img src="../../assets/company-logo.svg" alt="OOIN Logo" class="logo-img">
</div>
<h1 class="login-title">员工登录</h1>
<div class="login-subtitle">登录到OOIN数据库系统</div>
<form class="login-form" @submit.prevent="handleLogin">
<div class="form-group">
<label>用户名</label>
<input type="text" v-model="username" placeholder="请输入用户名">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" v-model="password" placeholder="请输入密码">
</div>
<div class="remember-me">
<input type="checkbox" id="remember" v-model="rememberMe">
<label for="remember">记住我</label>
<a href="#" class="forgot-password" @click.prevent="handleForgotPassword">忘记密码?</a>
</div>
<button type="submit" class="login-button">登录</button>
</form>
<div class="support-text">
遇到问题<a href="#" class="support-link" @click.prevent="contactSupport">联系技术支持</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'AdminLogin',
data() {
return {
username: '', //
password: '',
rememberMe: false
}
},
methods: {
handleLogin() {
// Mock authentication logic - in real app this would be an API call
const users = {
'admin1': { password: 'admin123', token: '0c2da9ad83fd142deb64c8f46f57b4a328ad6356', role: 'admin' },
'leader1': { password: 'leader123', token: '1fbc640baf1d35d67d4d4f957a7282e3cc84121a', role: 'leader' },
'leader2': { password: 'leader123', token: '8263ba67422324042ae5c063192dc694015ae2d2', role: 'leader' },
'member1': { password: 'member123', token: 'bd87c833d4a659f6434bc235f299533ba6c2fec2', role: 'member' },
'member2': { password: 'member123', token: 'fe986c6c1738bb18218415c908df11a20719bed8', role: 'member' }
};
const user = users[this.username]; //
if (user && user.password === this.password) {
// Store user info in localStorage
localStorage.setItem('userToken', user.token);
localStorage.setItem('userName', this.username); //
localStorage.setItem('userRole', user.role);
// Redirect to main page
this.$router.push('/');
} else {
// Show error message
alert('Invalid username or password');
}
},
handleForgotPassword() {
console.log('Forgot password clicked');
},
contactSupport() {
console.log('Contact support clicked');
}
}
}
</script>
<!-- <script>
export default {
name: 'AdminLogin',
data() {
return {
email: '',
password: '',
rememberMe: false
}
},
methods: {
handleLogin() {
console.log('Login attempt', {
email: this.email,
password: this.password,
rememberMe: this.rememberMe
});
},
handleForgotPassword() {
console.log('Forgot password clicked');
},
contactSupport() {
console.log('Contact support clicked');
}
}
}
</script> -->
<style lang="less" scoped>
.login-page {
min-height: 100vh;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: #000000;
padding: 20px;
box-sizing: border-box;
}
.login-container {
width: 100%;
max-width: 400px;
text-align: center;
.logo {
margin-bottom: 40px;
.logo-img {
width: 110px;
}
}
.login-title {
color: white;
font-size: 24px;
font-weight: 500;
margin: 0 0 10px;
}
.login-subtitle {
color: #888;
font-size: 14px;
margin-bottom: 40px;
}
.login-form {
background-color: #18181b;
padding: 30px;
border-radius: 8px;
text-align: left;
.form-group {
margin-bottom: 20px;
label {
display: block;
color: #888;
margin-bottom: 8px;
}
input {
width: 100%;
padding: 12px;
background-color: #27272a;
border: none;
border-radius: 4px;
color: white;
box-sizing: border-box;
&::placeholder {
color: #666;
}
}
}
.remember-me {
display: flex;
align-items: center;
margin-bottom: 20px;
color: #888;
input {
margin-right: 8px;
}
label {
margin-right: auto;
}
}
.forgot-password {
color: #ef4444;
text-decoration: none;
}
.login-button {
width: 100%;
padding: 12px;
background-color: #ef4444;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin-top: 20px;
&:hover {
background-color: darken(#ef4444, 5%);
}
}
}
.support-text {
margin-top: 20px;
font-size: 14px;
color: #666;
.support-link {
color: #ef4444;
text-decoration: none;
}
}
}
</style>

17
src/main.js Executable file
View File

@ -0,0 +1,17 @@
import Vue from 'vue';
import router from './router'
import store from './store/'
import { VueAxios } from "@/utils/request"
import Antd from 'ant-design-vue';
import App from './App';
import 'ant-design-vue/dist/antd.css';
Vue.config.productionTip = false;
Vue.use(Antd);
Vue.use(VueAxios);
/* eslint-disable no-new */
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')

93
src/router/index.js Executable file
View File

@ -0,0 +1,93 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import index from '../views/index'
Vue.use(VueRouter)
const routes = [
{
path: '/login',
name: 'UserLogin',
component: () => import('@/components/layouts/UserLogin.vue'),
meta: { title: "员工登录" }
},
{
path: '/chat',
component: () => import(/* webpackChunkName: "user" */ '@/views/chat'),
hidden: true,
children: [
{
path: 'chat',
name: 'chat',
meta: { title: "知识库问询" },
component: () => import(/* webpackChunkName: "user" */ '@/views/chat')
}
]
},
{
path: '/',
name: 'index',
component: () => import('@/components/layouts/BasicLayout'),
meta: { title: '知识库' },
redirect: '/list',
children: [
{
path: 'list',
name: 'list',
meta: { title: "知识库" },
component: () => import(/* webpackChunkName: "user" */ '@/views/list/list')
},
{
path: 'upload',
name: 'upload',
meta: { title: "上传文档" },
component: () => import(/* webpackChunkName: "user" */ '@/views/upload')
},
{
path: 'detail',
name: 'detail',
meta: { title: "知识库详情" },
component: () => import(/* webpackChunkName: "user" */ '@/views/detail')
},
{
path: 'history',
name: 'history',
meta: { title: "对话记录" },
component: () => import(/* webpackChunkName: "user" */ '@/views/history')
},
{
path: 'historyDetail',
name: 'historyDetail',
meta: { title: "对话记录详情" },
component: () => import(/* webpackChunkName: "user" */ '@/views/history/detail')
},
{
path: 'setting',
name: 'setting',
meta: { title: "对话记录详情" },
component: () => import(/* webpackChunkName: "user" */ '@/views/setting')
},
]
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
router.beforeEach((to, from, next) => {
const publicPages = ['/login'];
const authRequired = !publicPages.includes(to.path);
const loggedIn = localStorage.getItem('userToken');
if (authRequired && !loggedIn) {
return next('/login');
}
next();
});
export default router

25
src/store/getters.js Executable file
View File

@ -0,0 +1,25 @@
import Vue from 'vue'
import { USER_INFO, ENHANCE_PRE, ACCESS_TOKEN, MENU_DATA } from '@/store/mutation-types'
const getters = {
device: state => state.app.device,
theme: state => state.app.theme,
color: state => state.app.color,
token: state => {state.user.token = Vue.ls.get(ACCESS_TOKEN); return state.user.token},
avatar: state => {state.user.avatar = Vue.ls.get(USER_INFO).avatar; return state.user.avatar},
username: state => state.user.info.loginName,
nickname: state => {state.user.info.realname = Vue.ls.get(USER_INFO).username; return state.user.info.realname},
welcome: state => state.user.welcome,
permissionList: state => {state.user.permissionList = Vue.ls.get(MENU_DATA); return state.user.permissionList},
userInfo: state => {state.user.info = Vue.ls.get(USER_INFO); return state.user.info},
addRouters: state => state.permission.addRouters,
onlAuthFields: state => {return state.online.authFields },
enhanceJs:(state) => (code) => {
state.enhance.enhanceJs[code] = Vue.ls.get(ENHANCE_PRE+code);
return state.enhance.enhanceJs[code]
},
sysSafeMode: state => state.user.sysSafeMode,
uploadStatus: state => state.upload.uploadStatus,
savePosition: state => state.position.savePosition,
}
export default getters

35
src/store/index.js Executable file
View File

@ -0,0 +1,35 @@
import Vue from 'vue'
import Vuex from 'vuex'
// import app from './modules/app'
// import user from './modules/user'
// import permission from './modules/permission'
// import enhance from './modules/enhance'
// import online from './modules/online'
// import upload from './modules/upload'
// import position from './modules/position'
import getters from './getters'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
// app,
// user,
// permission,
// enhance,
// online,
// upload,
// position,
},
state: {
},
mutations: {
},
actions: {
},
getters
})

29
src/store/mutation-types.js Executable file
View File

@ -0,0 +1,29 @@
export const ACCESS_TOKEN = 'Access-Token'
export const SIDEBAR_TYPE = 'SIDEBAR_TYPE'
export const DEFAULT_THEME = 'DEFAULT_THEME'
export const DEFAULT_LAYOUT_MODE = 'DEFAULT_LAYOUT_MODE'
export const DEFAULT_COLOR = 'DEFAULT_COLOR'
export const DEFAULT_COLOR_WEAK = 'DEFAULT_COLOR_WEAK'
export const DEFAULT_FIXED_HEADER = 'DEFAULT_FIXED_HEADER'
export const DEFAULT_FIXED_SIDEMENU= 'DEFAULT_FIXED_SIDEMENU'
export const DEFAULT_FIXED_HEADER_HIDDEN = 'DEFAULT_FIXED_HEADER_HIDDEN'
export const DEFAULT_CONTENT_WIDTH_TYPE = 'DEFAULT_CONTENT_WIDTH_TYPE'
export const DEFAULT_MULTI_PAGE = 'DEFAULT_MULTI_PAGE'
export const USER_NAME = 'Login_Username'
export const MENU_DATA = 'menu_data'
export const USER_INFO = 'Login_Userinfo'
export const USER_AUTH = 'LOGIN_USER_BUTTON_AUTH'
export const SYS_BUTTON_AUTH = 'SYS_BUTTON_AUTH'
export const ENCRYPTED_STRING = 'ENCRYPTED_STRING'
export const ENHANCE_PRE = 'enhance_'
export const UI_CACHE_DB_DICT_DATA = 'UI_CACHE_DB_DICT_DATA'
export const INDEX_MAIN_PAGE_PATH = '/dashboard/analysis'
export const OAUTH2_LOGIN_PAGE_PATH = '/oauth2-app/login'
export const TENANT_ID = 'TENANT_ID'
export const ONL_AUTH_FIELDS = 'ONL_AUTH_FIELDS'
//路由缓存问题关闭了tab页时再打开就不刷新 #842
export const CACHE_INCLUDED_ROUTES = 'CACHE_INCLUDED_ROUTES'
export const CONTENT_WIDTH_TYPE = {
Fluid: 'Fluid',
Fixed: 'Fixed'
}

37
src/utils/axios.js Executable file
View File

@ -0,0 +1,37 @@
const VueAxios = {
vm: {},
// eslint-disable-next-line no-unused-vars
install(Vue, router = {}, instance) {
if (this.installed) {
return;
}
this.installed = true;
if (!instance) {
// eslint-disable-next-line no-console
console.error('You have to install axios');
return;
}
Vue.axios = instance;
Object.defineProperties(Vue.prototype, {
axios: {
get: function get() {
return instance;
}
},
$http: {
get: function get() {
return instance;
}
}
});
}
};
export {
VueAxios,
// eslint-disable-next-line no-undef
//instance as axios
}

29
src/utils/index.js Executable file
View File

@ -0,0 +1,29 @@
export function filterObj(obj) {
if (!(typeof obj == 'object')) {
return
}
for (let key in obj) {
if (obj.hasOwnProperty(key)
&& (obj[key] == null || obj[key] == undefined || obj[key] === '')) {
delete obj[key]
}
}
return obj
}
export function formatDateTime(datetimeStr, pattern) {
const date = new Date(datetimeStr);
const formatter = new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
pattern
});
return formatter.format(date);
}

53
src/utils/request.js Executable file
View File

@ -0,0 +1,53 @@
import Vue from 'vue'
import axios from 'axios'
import store from '@/store'
import { VueAxios } from './axios'
// import { ACCESS_TOKEN, TENANT_ID } from '@/store/mutation-types'
/**
* 指定 axios的 baseURL
* 如果手工指定 baseURL: '/jeecg-boot'
* 则映射后端域名通过 vue.config.js
* @type {*|string}
*/
// 创建 axios 实例
const service = axios.create({
baseURL: process.env.API, // api base_url
timeout: 9000, // 请求超时时间
})
// request interceptor
service.interceptors.request.use(config => {
// const token = Vue.ls.get(ACCESS_TOKEN)
// if (token) {
// config.headers['Authorization'] = 'Bearer ' + token // 让每个请求携带自定义 token 请根据实际情况自行修改
// }
return config
}, (error) => {
return Promise.reject(error)
})
// response interceptor
service.interceptors.response.use((response) => {
if (response.data.code !== 200) {
Vue.prototype.$Jnotification.error(
{ message: '系统提示', description: response.data.message, duration: 4 })
}
if (response.status == 200) {
return response.data
}
}, () => {
return Promise.reject('error')
})
const installer = {
vm: {},
install (Vue, router = {}) {
Vue.use(VueAxios, router, service)
},
}
export {
installer as VueAxios,
service as axios,
}

13
src/views/HomeViewNew.vue Executable file
View File

@ -0,0 +1,13 @@
<template>
</template>
<script>
export default {
data() {
return{
}
},
}
</script>

2194
src/views/chat/index.vue Executable file

File diff suppressed because it is too large Load Diff

325
src/views/detail/index.vue Executable file
View File

@ -0,0 +1,325 @@
<template>
<div class="detail">
<div class="file-list">
<div class="head" @click="$router.back()">
<a-icon type="left" class="a-icon"/>
<span class="title">返回知识库</span>
</div>
<p class="system-title">文档列表</p>
<div class="file-item" v-for="(item, index) in fileList" :key="index" :class="currentFileId==item.id?'active':''"
@click="switchFile(item.id)">
<a-tooltip placement="top">
<template slot="title">
<span>{{ item.name }}</span>
</template>
<span class="file-name">{{ item.name }}</span>
</a-tooltip>
<a-popconfirm title="确定删除吗?" @confirm="() => handleDelete(item.id)">
<a-icon type="delete"/>
</a-popconfirm>
</div>
</div>
<div class="file-content">
<div class="file-head">
<img src="../../assets/system.png" class="icon-file"/>
<p class="file-title">文档内容</p>
</div>
<div class="file-content-box">
<div class="file-content-item" v-for="(item, index) in currentFile" :key="index">
{{ item.content }}
</div>
</div>
</div>
</div>
</template>
<script>
import {documentDetail, documentFileDelete, documentFileDetail} from "../../api";
export default {
name: 'detail',
data() {
return {
id: '',
fileList: [],
currentFileId: '',
currentFile: []
}
},
created() {
this.id = this.$route.query && (this.$route.query.id)
this.getDetail()
},
methods: {
async handleDelete(id) {
try {
const res = await documentFileDelete({
fileId: id,
documentId: this.id
})
if (res.code === 200) {
this.$message.success(res.message)
await this.getDetail()
} else {
this.$message.error(res.message)
}
} catch (e) {
throw e
}
},
async getDetail() {
try {
const res = await documentDetail(this.id)
if (res.code === 200) {
this.fileList = res.data
if (this.fileList.length) {
this.currentFileId = this.fileList[0].id
this.getDocumentFileDetail()
}
} else {
this.$message.error(res.message)
}
} catch (e) {
throw e
}
},
switchFile(id) {
this.currentFileId = id
this.getDocumentFileDetail()
},
async getDocumentFileDetail() {
try {
const result = await documentFileDetail({
fileId: this.currentFileId,
documentId: this.id
})
if (result.code === 200) {
this.currentFile = result.data
}
} catch (e) {
throw e
}
}
}
}
</script>
<style scoped lang="less">
.detail {
width: 100%;
height: 100%;
display: flex;
.head {
margin-bottom: 14px;
text-align: left;
margin-top: 12px;
.a-icon {
cursor: pointer;
color: #a1a1aa;
}
.title {
font-family: Poppins, Poppins;
font-weight: 400;
font-size: 16px;
color: #a1a1aa;
line-height: 19px;
text-align: left;
font-style: normal;
}
}
.system-title{
font-family: Poppins, Poppins;
font-weight: 500;
font-size: 20px;
color: #ffffff;
line-height: 23px;
text-align: left;
font-style: normal;
position: relative;
margin-bottom: 20px;
}
.file-list {
display: flex;
flex-direction: column;
width: 220px;
height: 100%;
overflow-y: auto;
background: #18181b;
border-radius: 5px;
.file-item {
min-height: 50px;
height: 50px;
margin-right: 5px;
display: flex;
align-items: center;
color: #a1a1aa;
justify-content: center;
cursor: pointer;
padding: 0 5px 6px;
border-bottom: 1px solid #27272a;
.file-name {
flex: 1;
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
text-align: left;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2; /* 这里是超出几行省略 */
overflow: hidden;
}
&.active {
font-weight: bold;
color: #ef4444;
}
& + .file-item {
margin-top: 10px;
}
.file-name {
}
}
}
.file-content {
flex: 1;
margin-left: 10px;
background: #18181b;
border-radius: 5px;
padding: 46px 10px 20px;
overflow: hidden;
display: flex;
flex-direction: column;
.file-head{
border-bottom: 1px solid #27272a;
display: flex;
.icon-file{
width: 23px;
height: 23px;
margin-right: 6px;
}
.file-title{
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 20px;
color: #ffffff;
line-height: 23px;
margin-bottom:12px;
text-align: left;
font-style: normal;
}
}
.title {
text-align: left;
font-size: 16px;
color: #000;
margin-bottom: 10px;
position: relative;
padding-left: 14px;
&::before {
content: "";
position: absolute;
top: 2px;
height: 20px;
left: 0;
bottom: 0;
border-right: 3px solid #1890ff;
transform: scaleY(1);
opacity: 1;
transition: transform .15s cubic-bezier(.645, .045, .355, 1), opacity .15s cubic-bezier(.645, .045, .355, 1);
}
}
.file-content-box{
// flex: 1;
height: 480px;
padding:16px 14px 24px 20px;
overflow-y: scroll;
text-align: left;
text-indent:2em;
margin-top: 20px;
border: 1px solid #27272a;
color: #ffffff;
border-radius: 4px;
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-thumb {
background: #3f3f46; //
border-radius: 5px; //
}
.file-content-item {
padding: 10px 0;
& + .file-content-item {
border-top: 1px dashed #27272a;
}
}
}
}
}
//pop-up
:deep(.ant-popover) {
.ant-popover-content {
.ant-popover-inner {
background-color: #18181b !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
.ant-popover-inner-content {
color: #ffffff !important;
}
.ant-popover-message {
color: #ffffff !important;
.anticon-exclamation-circle {
color: #ef4444 !important;
}
}
.ant-popover-buttons {
.ant-btn {
&.ant-btn-default {
background: #3f3f46 !important;
border-color: #3f3f46 !important;
color: #ffffff !important;
&:hover {
background: #52525b !important;
}
}
&.ant-btn-primary {
background: #ef4444 !important;
border-color: #ef4444 !important;
color: #ffffff !important;
&:hover {
background: #dc2626 !important;
}
}
}
}
}
.ant-popover-arrow {
&::after {
background-color: #18181b !important;
box-shadow: none !important;
}
}
}
}
</style>

130
src/views/history/detail.vue Executable file
View File

@ -0,0 +1,130 @@
<template>
<div>
<div class="head">
<a-icon type="arrow-left" class="a-icon" @click="$router.back()"/>
<span class="title">对话记录详情</span>
</div>
<div>
<a-table
ref="table"
rowKey="id"
:columns="columns"
:dataSource="dataSource"
:pagination="false"
:loading="loading"
bordered
>
</a-table>
</div>
</div>
</template>
<script>
import {addDataset, deleteDataset, getChatRecord, getChatRecordByChatId, listPage} from "../../api";
import moment from "moment";
import {filterObj, formatDate, formatDateTime} from "../../utils";
export default {
data() {
return {
loading: false,
dataSource: [],
//
columns: [
{
title: '用户',
align: "center",
dataIndex: 'problem_text',
ellipsis: true,
customRender: (text) => (
<a-tooltip placement="topLeft" title={text}>
<span>{text}</span>
</a-tooltip>
)
},
{
title: '回复',
align: "center",
dataIndex: 'answer_text',
ellipsis: true,
customRender: (text) => (
<a-tooltip placement="topLeft" title={text}>
<span>{text}</span>
</a-tooltip>
)
},
{
title: '创建时间',
align: "center",
dataIndex: 'create_time',
customRender: (text, record) => {
return formatDateTime(text, 'yyyy-MM-dd HH:mm:ss')
}
},
{
title: '创建时间',
align: "center",
dataIndex: 'update_time',
customRender: (text, record) => {
return formatDateTime(text, 'yyyy-MM-dd HH:mm:ss')
}
}
],
chatId: ''
}
},
created() {
this.chatId = this.$route.query && (this.$route.query.chat_id)
if (this.chatId) {
this.loadData()
}
},
methods: {
async loadData() {
this.loading = true;
try {
const result = await getChatRecordByChatId({chatId: this.chatId})
if (result.code === 200) {
this.dataSource = result.data
} else {
this.$message.warning(result.message)
}
} catch (e) {
throw e
} finally {
this.loading = false
}
},
}
}
</script>
<style lang="less" scoped>
/deep/ .ant-table {
background: #fff;
.ant-table-thead > tr > th, .ant-table-tbody > tr > td {
padding: 10px;
}
}
.title {
text-align: left;
font-size: 18px;
color: #000;
margin-bottom: 16px;
position: relative;
padding-left: 14px;
}
.head {
text-align: left;
margin-bottom: 16px;
.mr10 {
margin-right: 10px;
}
}
</style>

146
src/views/history/index.vue Executable file
View File

@ -0,0 +1,146 @@
<template>
<div>
<p class="title">对话记录</p>
<div>
<a-table
ref="table"
rowKey="id"
:columns="columns"
:dataSource="dataSource"
:pagination="false"
:loading="loading"
bordered
>
<span slot="action" slot-scope="text, record">
<a-button type="link" @click="detail(record.chat_id)">详情</a-button>
</span>
</a-table>
</div>
</div>
</template>
<script>
import {addDataset, deleteDataset, getChatRecord, listPage} from "../../api";
import moment from "moment";
import {filterObj, formatDate, formatDateTime} from "../../utils";
export default {
data() {
return {
loading: false,
dataSource: [],
//
columns: [
{
title: '摘要',
align: "center",
dataIndex: 'abstract'
},
{
title: '聊天记录条数',
align: "center",
dataIndex: 'chat_record_count'
},
{
title: '创建时间',
align: "center",
dataIndex: 'create_time',
customRender: (text, record) => {
return formatDateTime(text, 'yyyy-MM-dd HH:mm:ss')
}
},
{
title: '创建时间',
align: "center",
dataIndex: 'update_time',
customRender: (text, record) => {
return formatDateTime(text, 'yyyy-MM-dd HH:mm:ss')
}
},
{
title: '操作',
dataIndex: 'action',
align: "center",
fixed: "right",
width: 80,
scopedSlots: {customRender: 'action'},
}
],
}
},
created() {
this.loadData()
},
methods: {
detail(chat_id) {
this.$router.push({
path: '/historyDetail',
query: {
chat_id
}
})
},
async loadData() {
var params = {
history_day: 7
}//
this.loading = true;
try {
const result = await getChatRecord(params)
if (result.code === 200) {
this.dataSource = result.data
} else {
this.$message.warning(result.message)
}
} catch (e) {
throw e
} finally {
this.loading = false
}
},
}
}
</script>
<style lang="less" scoped>
/deep/ .ant-table {
background: #fff;
.ant-table-thead > tr > th, .ant-table-tbody > tr > td {
padding: 10px;
}
}
.title {
text-align: left;
font-size: 18px;
color: #000;
margin-bottom: 16px;
position: relative;
padding-left: 14px;
&::before {
content: "";
position: absolute;
top: 2px;
height: 20px;
left: 0;
bottom: 0;
border-right: 3px solid #1890ff;
transform: scaleY(1);
opacity: 1;
transition: transform .15s cubic-bezier(.645, .045, .355, 1), opacity .15s cubic-bezier(.645, .045, .355, 1);
}
}
.head {
text-align: left;
margin-bottom: 16px;
.mr10 {
margin-right: 10px;
}
}
</style>

130
src/views/index/index.vue Executable file
View File

@ -0,0 +1,130 @@
<template>
<div class="main">
<template v-if="isWebPage">
<div class="main-head" >
<img src="../../assets/logo-icon.png" alt="" class="logo">
<div class="right">
<a-icon type="search" class="icon-ball" />
<a-icon type="bell" class="icon-ball" />
<img src="../../assets/logo-icon.png" alt="" class="icon-user">
<a-dropdown>
<a class="ant-dropdown-link" @click="e => e.preventDefault()">
Marci Fumons <a-icon type="down" />
</a>
<a-menu slot="overlay">
<a-menu-item>
<a href="javascript:;">退出登陆</a>
</a-menu-item>
<a-menu-item>
<a href="javascript:;">消息</a>
</a-menu-item>
</a-menu>
</a-dropdown>
</div>
</div>
<div class="main-con">
<div class="main-menu">
<a-menu style="width: 256px" :default-selected-keys="['1']" :default-open-keys="['sub1']" mode="inline"
:selected-keys="[current]" @click="handleClick">
<a-menu-item key="1">
<a-icon type="profile" />
知识库
</a-menu-item>
<a-menu-item key="2">
<a-icon type="setting" />
设置
</a-menu-item>
</a-menu>
</div>
<div class="main-right">
<router-view />
</div>
</div>
</template>
<template v-else>
<router-view />
</template>
</div>
</template>
<script>
export default {
data() {
return {
current: '1',
isWebPage:true,
menuList: [
{ name: '知识库', path: '/list' },
{ name: '对话记录', path: '/history' },
]
}
},
created() {
},
methods: {
handleClick(e) {
console.log('click ', e);
this.current = e.key;
},
},
}
</script>
<style lang="less" scoped>
.main {
height: 100%;
width: 100%;
.main-head {
display: flex;
justify-content: space-between;
border-bottom: 1px solid #E6EDFF;
padding: 0 50px;
box-sizing: border-box;
height: 60px;
.logo {
// background: url('../../assets/logo-icon.png') no-repeat center center;
// background-size: 100% 100%;
width: 110px;
height: 34px;
margin-top: 13px;
}
.right {
margin-top: 14px;
.icon-ball {
font-size: 16px;
margin-right: 28px;
}
.icon-user {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 12px;
}
}
}
.main-con {
display: flex;
.main-menu {
width: 285px;
text-align: left;
.ant-menu-inline .ant-menu-item {
padding-left: 58px !important;
}
}
.main-con-right {
flex: 1;
}
}
}
</style>

760
src/views/list/list.vue Executable file
View File

@ -0,0 +1,760 @@
<template>
<div>
<!-- <p class="welcome">欢迎来到OOIN小助手</p> -->
<!-- <p class="welcome-en">Welcome to the The little assistant who speaks to each other.</p> -->
<div class="table-con">
<div class="head">
<a-button type="primary" @click="handleAdd" class="add-btn">创建知识库</a-button>
</div>
<a-table
ref="table"
rowKey="id"
:columns="columns"
:dataSource="dataSource"
:pagination="ipagination"
:loading="loading"
@change="handleTableChange"
>
<template slot="status" slot-scope="t">
<a-icon type="check-circle" theme="twoTone" two-tone-color="#52c41a"/>
<span>{{ t.status }}</span>
</template>
<template slot="openStatus" slot-scope="t">
<a-switch checkedChildren="是" unCheckedChildren="否" v-model="t.openStatus"/>
</template>
<span slot="action" slot-scope="text, record">
<a-button type="link" @click="handleUpload(record.id)" style="padding: 0">添加</a-button>
<a-button type="link" @click="handleDetail(record.id)" style="padding: 0">详情</a-button>
<!-- <a-button type="link" @click="handleChat(record)" style="padding: 0">问询</a-button> -->
<a-button type="link" style="padding: 0" @click="handleDel(record.id)">删除</a-button>
</span>
</a-table>
</div>
<a-modal :visible="createAddVisible" @cancel="handleCancel" @ok="handleSave"
:confirmLoading="confirmLoading">
<a-form-model :model="model" ref="form" :rules="validatorRules">
<a-form-model-item label="名称" prop="name">
<a-input v-model="model.name" placeholder="请输入名称"></a-input>
</a-form-model-item>
<a-form-model-item label="描述" prop="desc">
<a-input type="textarea" v-model="model.desc" placeholder="请输入描述"></a-input>
</a-form-model-item>
</a-form-model>
</a-modal>
<Upload ref="upload" @uploadSave="uploadSave"></Upload>
<a-modal
title="提示"
:visible="visible"
:confirm-loading="confirmLoading"
@ok="handleDelete"
@cancel="handleDelCancel"
>
<div class="del-box">
<a-icon type="info-circle" theme="twoTone" two-tone-color="red" style="font-size: 20px;"/>
<p class="del-title">
确定删除吗?
</p>
</div>
</a-modal>
</div>
</template>
<script>
import {addDataset, deleteDataset, listPage} from "../../api";
import moment from "moment";
import {filterObj, formatDateTime} from "../../utils";
import Upload from '../upload'
export default {
data() {
return {
confirmLoading: false,
visible:false,
delId:'',
validatorRules: {
name: [
{required: true, message: '请输入知识库名称'}
],
desc: [
{required: true, message: '请输入知识库描述'}
],
},
defaultModel: {
name: '',
desc: '',
embedding_mode_id: '42f63a3d-427e-11ef-b3ec-a8a1595801ab'
},
model: {},
createAddVisible: false,
createAddUploadVisible:false,
/* 分页参数 */
ipagination: {
current: 1,
pageSize: 10,
//pageSizeOptions: ['10', '20', '30'],
showTotal: (total, range) => {
return " 共" + total + "条"
},
//showSizeChanger: true,
total: 0
},
loading: false,
dataSource: [],
//
queryParam: {name: '',},
//
columns: [
{
title: 'NO',
key: 'index',
width: 80,
customRender: (text, record, index) => `${index + 1}`
},
{
title: '知识库名称',
dataIndex: 'name'
},
{
title: '描述',
dataIndex: 'desc'
},
{
title: '文档个数',
dataIndex: 'document_count'
},
{
title: '创建时间',
dataIndex: 'create_time',
customRender: (text, record) => {
return formatDateTime(text, 'yyyy-MM-dd HH:mm:ss')
}
},
{
title: '更新时间',
dataIndex: 'update_time',
customRender: (text, record) => {
return formatDateTime(text, 'yyyy-MM-dd HH:mm:ss')
}
},
{
title: '操作',
dataIndex: 'action',
fixed: "right",
width: 160,
scopedSlots: {customRender: 'action'},
}
],
}
},
components: {Upload},
created() {
this.loadData()
},
methods: {
handleDel(id){
this.delId = id
this.visible = true
},
handleDelCancel(){
this.visible = false
},
uploadSave(){
this.loadData()
},
handleDetail(id) {
this.$router.push({
path: '/detail',
query: {
id: id
}
})
},
handleChat(record) {
this.$router.push({
path: '/chat',
query: {
id: record.id
}
})
},
getQueryParams() {
var param = Object.assign({}, this.queryParam)
param.pageNum = this.ipagination.current
param.pageSize = this.ipagination.pageSize
return filterObj(param)
},
async loadData(arg) {
if (arg === 1) {
this.ipagination.current = 1;
}
var params = this.getQueryParams()//
this.loading = true;
try {
const result = await listPage(params)
if (result.code === 200) {
this.dataSource = result.data.records.filter(item => item.name !== 'sql');
this.ipagination.total = this.dataSource.length;
} else {
this.$message.warning(result.message)
}
} catch (e) {
throw e
} finally {
this.loading = false
}
},
handleSave() {
this.$refs.form.validate(async ok => {
if (ok) {
this.confirmLoading = true
try {
const result = await addDataset(this.model)
if (result.code === 200) {
this.$message.success('新增成功')
this.handleCancel()
} else {
this.$message.warning(result.message)
}
} catch (e) {
throw e
} finally {
this.confirmLoading = false
}
}
})
},
handleCancel() {
this.createAddVisible = false
this.model = Object.assign({}, this.defaultModel)
this.loadData()
},
handleAdd() {
this.createAddVisible = true
this.model = Object.assign({}, this.defaultModel)
},
handleUpload(id) {
this.$refs.upload.add(id)
},
async handleDelete() {
try {
const result = await deleteDataset({id:this.delId})
if (result.code === 200) {
this.$message.success('删除成功')
this.handleDelCancel()
} else {
this.$message.warning(result.message)
}
} catch (e) {
throw e
} finally {
this.loadData()
}
},
handleTableChange(pagination, filters, sorter) {
this.ipagination = pagination
this.loadData()
}
}
}
</script>
<style lang="less" scoped>
/deep/ .ant-table {
background: #18181b; //
.ant-table-thead > tr > th, .ant-table-tbody > tr > td {
padding: 10px;
color: #ffffff; //
}
//update table specific styles
.ant-table-thead > tr > th {
background: #27272a !important;
color: #a1a1aa !important;
border-bottom: 1px solid #27272a;
}
.ant-table-tbody > tr > td {
background: #18181b;
border-bottom: 1px solid #27272a;
}
.ant-table-tbody > tr:hover > td {
background: #27272a !important;
}
//pagination style
/deep/ .ant-pagination-item {
background: #272721;
border-color: #3f3f46;
a {
color: #ffffff;
}
&:hover {
border-color: #ef4444;
}
}
/deep/ .ant-pagination-item-active {
background: #ef4444;
border-color: #ef4444;
}
//button styles
/deep/ .ant-btn-primary {
background: #ef4444;
border-color: #ef4444;
&:hover {
background: #dc2626;
border-color: #dc2626;
}
}
/deep/ .ant-btn-link {
&:first-child {
color: #ef4444;
&:hover {
color: #dc2626;
}
}
&:not(:first-child) {
color: #ef4444;
&:hover {
color: #dc2626;
}
}
}
.welcome{
font-family: arial, arial;
font-weight: 500;
font-size: 28px;
color: #ef4444;
line-height: 42px;
text-align: left;
font-style: normal;
padding-top: 12px;
margin-bottom: 24px;
}
.welcome-en{
font-family: arial, arial; //
font-weight: 400;
font-size: 16px;
color: #a1a1aa; //
line-height: 24px;
text-align: left;
font-style: normal;
margin-bottom: 22px;
}
// .title {
// text-align: left;
// font-size: 18px;
// color: #000;
// margin-bottom: 16px;
// position: relative;
// padding-left: 14px;
// &::before {
// content: "";
// position: absolute;
// top: 2px;
// height: 20px;
// left: 0;
// bottom: 0;
// border-right: 3px solid #1890ff;
// transform: scaleY(1);
// opacity: 1;
// transition: transform .15s cubic-bezier(.645, .045, .355, 1), opacity .15s cubic-bezier(.645, .045, .355, 1);
// }
// }
.table-con{
background: #18181b; //
border-radius: 10px;
border: 1px solid #27272a; //
padding:20px;
}
.head {
text-align: left;
margin-bottom: 0 0 32px 0;
.add-btn{
// width: 80px;
height: 34px;
background: #ef4444; //
box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.2); //
border-radius: 4px;
&:hover {
background: #dc2626;
}
}
}
// .mr10 {
// margin-right: 10px;
// }
// }
// /deep/.ant-table-thead > tr > th{
// background: #DCE7FF !important;
// }
:deep(.ant-modal) {
.ant-modal-content {
background-color: #18181b !important;
.ant-modal-header {
background-color: #18181b !important;
border-bottom: 1px solid #27272a !important;
.ant-modal-title {
color: #ffffff !important;
}
}
.ant-modal-body {
background-color: #18181b !important;
.ant-form-item-label > label {
color: #ffffff !important;
}
.ant-input {
background-color: #27272a !important;
border-color: #3f3f46 !important;
color: #ffffff !important;
&:hover, &:focus {
border-color: #ef4444 !important;
}
&::placeholder {
color: #71717a !important;
}
}
.ant-input-textarea {
textarea {
background-color: #27272a !important;
border-color: #3f3f46 !important;
color: #ffffff !important;
&:hover, &:focus {
border-color: #ef4444 !important;
}
&::placeholder {
color: #71717a !important;
}
}
}
}
.ant-modal-footer {
background-color: #18181b !important;
border-top: 1px solid #27272a !important;
.ant-btn-default {
background-color: #27272a !important;
border-color: #3f3f46 !important;
color: #ffffff !important;
&:hover {
background-color: #3f3f46 !important;
border-color: #52525b !important;
}
}
.ant-btn-primary {
background-color: #ef4444 !important;
border-color: #ef4444 !important;
&:hover {
background-color: #dc2626 !important;
border-color: #dc2626 !important;
}
}
}
}
.ant-modal-close {
color: #a1a1aa !important;
&:hover {
color: #ef4444 !important;
}
}
}
.del-box {
display: flex;
margin: 0 40px;
padding: 20px 0;
align-items: center;
.del-title {
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 16px;
color: #ffffff;
line-height: 19px;
text-align: left;
font-style: normal;
margin-bottom: 0;
margin-left: 12px;
}
}
// // Add styles for action buttons
// /deep/ {
// .ant-btn-link {
// &:first-child {
// color: #60a5fa; // Blue for view button
// &:hover {
// color: #3b82f6;
// }
// }
// &:not(:first-child) {
// color: #ef4444; // Red for other actions
// &:hover {
// color: #dc2626;
// }
// }
}
/deep/ .ant-table-pagination.ant-pagination {
//background-color: #18181b;
//padding: 16px;
display: flex;
justify-content: flex-end;
align-items: center;
padding: 16px 24px;
background-color: #27272a !important;
border-top: 1px solid #27272a;
margin: 0 !important;
width: 100%;
position: relative;
// Left side total count
.ant-pagination-total-text {
position: absolute;
left: 24px;
color: #a1a1aa;
}
// New section for prev/next buttons
.ant-pagination-prev,
.ant-pagination-next {
min-width: 32px;
height: 32px;
background: #3f3f46;
border: none;
border-radius: 4px;
margin: 0 4px;
.ant-pagination-item-link {
background: transparent;
color: #ffffff;
border: none;
}
&:hover {
background: #52525b;
}
}
.ant-pagination-item {
//background-color: #27272a;
//border-color: #27272a;
min-width: 32px;
height: 32px;
line-height: 32px;
background: #3f3f46;
border: none;
border-radius: 4px;
margin: 0 4 px;
a {
color: #ffffff;
}
&:hover {
background-color: #52525b;
}
&.ant-pagination-item-active {
background-color: #ef4444;
//border-color: #ef4444;
&:hover {
background-color: #dc2626;
}
}
}
// Next and Prev buttons
// .ant-pagination-prev,
// .ant-pagination-next {
// min-width: 32px;
// height: 32px;
// line-height: 32px;
// border-radius: 4px;
// background-color: #27272a;
// margin: 0 4px;
// .ant-pagination-item-link {
// background-color: transparent;
// border: none;
// color: #ffffff;
// }
// &:hover {
// background-color: #3f3f46;
// }
// }
// dropdown
.ant-pagination-options {
&::after {
content: "10 条/页";
color: #a1a1aa;
margin-left: 16px;
font-size: 14px;
line-height: 32px;
}
}
// margin-left: 16px;
// background: #ef4444;
// border-radius: 4px;
// padding: 0 8px;
// height: 32px;
// line-height: 32px;
// //color: #a1a1aa;
// .ant-select-selector {
// background-color: #ef4444 !important;
// border: none !important;
// border-radius: 4px !important;
// //color: #ffffff !important;
// height: 32px !important;
// padding: 0 12px !important;
// //line-height: 32px !important;
// }
// .ant-select-selection-item {
// color: #ffffff !important;
// line-height: 30px !important;
// }
// .ant-select-arrow {
// color: #ffffff;
// right: 12px;
// }
}
</style>
<style lang="less" scoped>
// All your existing styles remain exactly the same here
// Including that closing curly brace }
</style>
<!-- Add this new style block right here -->
<style lang="less">
.ant-modal {
.ant-modal-content {
background-color: #18181b !important;
.ant-modal-header {
background-color: #18181b !important;
border-bottom: 1px solid #27272a !important;
.ant-modal-title {
color: #ffffff !important;
}
}
.ant-modal-body {
background-color: #18181b !important;
.ant-form-item-label > label {
color: #ffffff !important;
}
.ant-input {
background-color: #27272a !important;
border-color: #3f3f46 !important;
color: #ffffff !important;
&:hover, &:focus {
border-color: #ef4444 !important;
}
&::placeholder {
color: #71717a !important;
}
}
.ant-input-textarea {
textarea {
background-color: #27272a !important;
border-color: #3f3f46 !important;
color: #ffffff !important;
&:hover, &:focus {
border-color: #ef4444 !important;
}
&::placeholder {
color: #71717a !important;
}
}
}
}
.ant-modal-footer {
background-color: #18181b !important;
border-top: 1px solid #27272a !important;
.ant-btn-default {
background-color: #27272a !important;
border-color: #3f3f46 !important;
color: #ffffff !important;
&:hover {
background-color: #3f3f46 !important;
border-color: #52525b !important;
}
}
.ant-btn-primary {
background-color: #ef4444 !important;
border-color: #ef4444 !important;
&:hover {
background-color: #dc2626 !important;
border-color: #dc2626 !important;
}
}
}
}
.ant-modal-close {
color: #a1a1aa !important;
&:hover {
color: #ef4444 !important;
}
}
}
</style>

37
src/views/setting/index.vue Executable file
View File

@ -0,0 +1,37 @@
<template>
<div >
<!-- <p class="welcome">欢迎来到OOIN小助手</p> -->
<!-- <p class="welcome-en">Welcome to the The little assistant who speaks to each other.</p> -->
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.welcome{
font-family: arial, arial;
font-weight: 500;
font-size: 28px;
color: #ef4444;
line-height: 42px;
text-align: left;
font-style: normal;
padding-top: 12px;
margin-bottom: 24px;
}
.welcome-en{
font-family: Poppins, Poppins;
font-weight: 400;
font-size: 16px;
color: #7C8DB5;
line-height: 24px;
text-align: left;
font-style: normal;
margin-bottom: 22px;
}
</style>

410
src/views/upload/index.vue Executable file
View File

@ -0,0 +1,410 @@
<template>
<a-modal title="添加知识库文档" :visible="createAddUploadVisible" @cancel="handleClose" @ok="handleSave"
:confirmLoading="confirmLoading" width="1000px">
<div class="upload">
<!-- <div class="head" @click="handleClose">
<a-icon type="arrow-left"/>
<span class="head-title">添加知识库文档</span>
</div> -->
<div class="content">
<div class="content-box">
<!-- <p class="title">上传文档</p> -->
<a-upload-dragger name="file" :file-list="fileList" ref="file" :accept="accept" :multiple="true" :action="uploadUrl"
@change="handleChange">
<p class="ant-upload-drag-icon">
<a-icon type="inbox" />
</p>
<p class="ant-upload-text">
将文件拖拽至此区域或 <span style="color: #4e7cff;">选择文件上传</span>
</p>
<p class="ant-upload-hint">
支持格式: DOCXDOCXLSXLSXPDFPPTPPTX
</p>
</a-upload-dragger>
</div>
<div class="right">
<div class="file-head">
<img src="../../assets/system.png" class="icon-file" />
<p class="file-title">文档内容</p>
</div>
<div class="file-box">
<template v-if="fileList.length">
<div class="file-item" v-for="(item, index) in fileList"
v-if="item.status == 'done' && item.response.code == 200" :key="index">
<span class="name">{{ item.response.data[0].name }}</span>
<div class="file-content" v-for="(item1, index1) in item.response.data[0].content" :key="index1">
<span class="title">{{ item1.title }}</span>
<span class="content">{{ item1.content }}</span>
</div>
</div>
</template>
<div class="default" v-else>
<div class="default-box">
<img src="../../assets/default.png" />
<p class="default-title">暂无内容</p>
</div>
</div>
</div>
</div>
</div>
<!-- <div class="footer">
<a-button class="mr20" @click="handleClose">取消</a-button>
<a-button type="primary" @click="handleSave">确定</a-button>
</div> -->
</div>
</a-modal>
</template>
<script>
import { addDocument } from "../../api";
export default {
data() {
return {
uploadUrl: `${process.env.API}/api/dataset/document/split`,
fileList: [],
accept: '.docx,.doc,.pdf,.ppt,.pptx,xls,.xlsx',
confirmLoading: false,
createAddUploadVisible: false,
}
},
methods: {
add(id) {
this.createAddUploadVisible = true;
this.id = id
this.fileList = []
},
handleChange(info) {
const status = info.file.status;
this.fileList = info.fileList;
if (status !== 'uploading') {
}
if (status === 'done') {
} else if (status === 'error') {
this.$message.error(`${info.file.name} 上传失败.`);
}
},
handleSave() {
if(!this.fileList.length) return this.$message.error('请上传知识库文档!');
let id = this.id
if (!id) {
this.$message.error('该知识不存在');
return
}
let totalNum = this.fileList.length;
let num = 0
this.fileList.forEach(async item => {
if (item.status == 'done' && item.response.code == 200) {
try {
let params = {
id,
name: item.response.data[0].name,
paragraphs: item.response.data[0].content
}
const result = await addDocument(params)
if (result.code == 200) {
this.$message.success(`${item.response.data[0].name} 添加成功`);
this.$emit('uploadSave')
this.handleClose()
} else {
this.$message.error(`${item.response.data[0].name} 添加失败`);
}
} catch (e) {
throw e
} finally {
num++
if (num == totalNum) {
setTimeout(() => {
// this.$router.go(-1)
this.createAddUploadVisible = false
})
}
}
}
})
},
handleClose() {
this.createAddUploadVisible = false
this.fileList = []
}
}
}
</script>
<style lang="less" scoped>
/deep/ .ant-modal {
.ant-modal-content {
background-color: #18181b !important;
}
.ant-modal-header {
background-color: #18181b !important;
border-bottom: 1px solid #27272a !important;
.ant-modal-title {
color: #ffffff !important;
}
}
.ant-modal-footer {
background-color: #27272a !important;
border-top: 1px solid #27272a !important;
}
}
/deep/ .ant-upload.ant-upload-drag {
background-color: #27272a !important;
border-color: #3f3f46 !important;
&:hover {
border-color: #ef4444 !important;
}
.ant-upload-text {
color: #ffffff !important;
}
.ant-upload-hint {
color: #a1a1aa !important;
}
.ant-upload-drag-icon {
color: #a1a1aa !important;
}
}
.upload {
display: flex;
flex-direction: column;
height: 360px;
overflow-y: auto;
.head {
background: #fafafa;
text-align: left;
padding: 10px 20px;
cursor: pointer;
margin: 0;
.head-title {
font-size: 16px;
font-weight: 600;
color: #000;
margin-left: 10px;
}
}
&>.content {
flex: 1;
width: 100%;
height: 0;
margin: 0 auto;
background: #18181b;
display: flex;
align-items: flex-start;
.right {
width: 400px;
margin-left: 20px;
display: flex;
flex-direction: column;
height: 100%;
.file-head {
border-bottom: 1px solid #27272a;
display: flex;
.icon-file {
width: 23px;
height: 23px;
margin-right: 6px;
}
.file-title {
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 20px;
color: #ffffff;
margin-bottom: 12px;
line-height: 23px;
text-align: left;
font-style: normal;
}
}
// & > .title {
// text-align: left;
// font-size: 16px;
// color: #000;
// margin-bottom: 16px;
// position: relative;
// padding-left: 14px;
// &::before {
// content: "";
// position: absolute;
// top: 2px;
// height: 20px;
// left: 0;
// bottom: 0;
// border-right: 3px solid #1890ff;
// transform: scaleY(1);
// opacity: 1;
// transition: transform .15s cubic-bezier(.645, .045, .355, 1), opacity .15s cubic-bezier(.645, .045, .355, 1);
// }
// }
.file-box {
flex: 1;
overflow-y: auto;
padding-right: 10px;
position: relative;
margin-top: 16px;
border: 1px solid #27272a;
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-thumb {
background: #3f3f46; //
border-radius: 5px; //
}
.file-item {
padding: 10px;
margin-bottom: 15px;
border-radius: 5px;
.name {
color: #ffffff;
font-weight: bold;
font-size: 18px;
}
.file-content {
color: #a1a1aa;
padding: 10px;
display: flex;
flex-direction: column;
text-indent: 2em;
&+.file-content {
border-top: 1px dashed #27272a;
}
.content {
font-size: 14px;
}
}
}
}
}
.content-box {
flex: 1;
}
}
.footer {
padding: 10px 20px;
text-align: right;
background: #fafafa;
.mr20 {
margin-right: 20px;
}
}
}
.default {
position: relative;
width: 100%;
height: 100%;
flex: 1;
.default-box {
position: absolute;
top: 50%;
/* 使图片顶部在容器中垂直居中 */
left: 50%;
/* 使图片左侧在容器中水平居中 */
transform: translate(-50%, -50%);
text-align: center;
img{
width: 120px;
height: 65px;
margin-bottom: 2px;
}
.default-title{
font-family: PingFang HK, PingFang HK;
font-weight: 400;
font-size: 12px;
color: #a1a1aa;
line-height: 20px;
text-align: left;
font-style: normal;
text-align: center;
margin: 0;
}
}
}
//pop-up
:deep(.ant-popover) {
.ant-popover-content {
.ant-popover-inner {
background-color: #18181b !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
.ant-popover-inner-content {
color: #ffffff !important;
}
.ant-popover-message {
color: #ffffff !important;
.anticon {
color: #ef4444 !important;
}
}
.ant-popover-buttons {
.ant-btn {
&.ant-btn-default {
background: #27272a !important;
border-color: #3f3f46 !important;
color: #ffffff !important;
&:hover {
background: #3f3f46 !important;
}
}
&.ant-btn-primary {
background: #ef4444 !important;
border-color: #ef4444 !important;
&:hover {
background: #dc2626 !important;
}
}
}
}
}
.ant-popover-arrow {
&::after {
background-color: #18181b !important;
box-shadow: none !important;
}
}
}
}
</style>

0
static/.gitkeep Executable file
View File

8350
yarn.lock Executable file

File diff suppressed because it is too large Load Diff