Compare commits
No commits in common. 'main' and 'linfangfang_branch' have entirely different histories.
main
...
linfangfan
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
VITE_ZHIPU_API_KEY=bfde4da145ae42449f0c5bf86d271555.1U5SaZQNaOOPl0kT
|
||||
VITE_ALIYUN_API_KEY=sk-dbf4c6aa70b34de9a9eba5841001be41
|
||||
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
# Vue 3 + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).
|
||||
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Vue</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "oc-community-frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.12.2",
|
||||
"element-plus": "^2.11.2",
|
||||
"vue": "^3.5.18",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"vite": "^7.1.2"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.7 MiB |
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
After Width: | Height: | Size: 496 B |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<div class="contacts">
|
||||
<div class="contacts-header">
|
||||
<el-input v-model="keyword" placeholder="搜索联系人" clearable />
|
||||
</div>
|
||||
<el-scrollbar class="contacts-scroll">
|
||||
<div
|
||||
v-for="c in filtered"
|
||||
:key="c.id"
|
||||
class="contact-item"
|
||||
:class="{ active: c.id === modelValue }"
|
||||
@click="select(c.id)"
|
||||
>
|
||||
<img class="avatar" :src="c.avatar" alt="avatar" />
|
||||
<div class="meta">
|
||||
<div class="name">{{ c.name }}</div>
|
||||
<div v-if="!simpleMode" class="last">{{ c.lastMessage || '...' }}</div>
|
||||
</div>
|
||||
<div v-if="!simpleMode && c.lastTime" class="time">{{ formatTime(c.lastTime) }}</div>
|
||||
<el-badge v-if="!simpleMode && c.unread" :value="c.unread" class="unread" />
|
||||
</div>
|
||||
<div v-if="filtered.length === 0" class="empty">无匹配联系人</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
contacts: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
simpleMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const keyword = ref('')
|
||||
const filtered = computed(() => {
|
||||
const k = keyword.value.trim().toLowerCase()
|
||||
if (!k) return props.contacts
|
||||
return props.contacts.filter(c =>
|
||||
c.name.toLowerCase().includes(k)
|
||||
)
|
||||
})
|
||||
|
||||
const select = id => emit('update:modelValue', id)
|
||||
|
||||
const formatTime = ts => {
|
||||
try {
|
||||
const d = new Date(ts)
|
||||
const h = String(d.getHours()).padStart(2, '0')
|
||||
const m = String(d.getMinutes()).padStart(2, '0')
|
||||
return `${h}:${m}`
|
||||
} catch (e) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.contacts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
border-right: 1px solid #ebeef5;
|
||||
background: #fff;
|
||||
}
|
||||
.contacts-header {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
.contacts-scroll {
|
||||
height: 100%;
|
||||
}
|
||||
.contact-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.contact-item:hover {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
.contact-item.active {
|
||||
background: #ecf5ff;
|
||||
}
|
||||
.avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.meta {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.name {
|
||||
font-weight: 600;
|
||||
}
|
||||
.last {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.time {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
.empty {
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div class="home-container">
|
||||
<Navigation />
|
||||
<div class="content">
|
||||
<h2>首页</h2>
|
||||
<el-space>
|
||||
<el-button type="primary" @click="goCreate">去创建OC</el-button>
|
||||
<el-button @click="goCommunity">进入社区</el-button>
|
||||
<el-button type="success" @click="goPhone">打开手机</el-button>
|
||||
</el-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import Navigation from './Navigation.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const goCreate = () => router.push('/create-oc')
|
||||
const goCommunity = () => router.push('/community')
|
||||
const goPhone = () => router.push('/phone')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<div class="login-card">
|
||||
<div class="login-header">
|
||||
<h2>{{ isLoginMode ? '登录' : '注册' }}</h2>
|
||||
<p>{{ isLoginMode ? '欢迎回来!' : '创建新账户' }}</p>
|
||||
</div>
|
||||
|
||||
<el-form :model="form" label-width="80px" class="login-form">
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="form.username" placeholder="请输入用户名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码">
|
||||
<el-input v-model="form.password" type="password" placeholder="请输入密码" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 注册时才显示的字段 -->
|
||||
<template v-if="!isLoginMode">
|
||||
<el-form-item label="昵称">
|
||||
<el-input v-model="form.nickname" placeholder="请输入昵称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="生日">
|
||||
<el-date-picker
|
||||
v-model="form.birthday"
|
||||
type="date"
|
||||
placeholder="选择生日"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="职业">
|
||||
<el-input v-model="form.occupation" placeholder="请输入职业" />
|
||||
</el-form-item>
|
||||
<el-form-item label="喜好">
|
||||
<el-input v-model="form.hobbies" placeholder="请输入你的喜好" />
|
||||
</el-form-item>
|
||||
<el-form-item label="个人简介">
|
||||
<el-input
|
||||
v-model="form.bio"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="介绍一下自己吧"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="isLoginMode ? handleLogin() : handleRegister()"
|
||||
:loading="loading"
|
||||
class="submit-btn"
|
||||
>
|
||||
{{ isLoginMode ? '登录' : '注册' }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="text" @click="toggleMode" class="toggle-btn">
|
||||
{{ isLoginMode ? '还没有账户?点击注册' : '已有账户?点击登录' }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElLoading } from 'element-plus'
|
||||
import { login, register } from '../stores/userStore.js'
|
||||
|
||||
const router = useRouter()
|
||||
const form = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
nickname: '',
|
||||
birthday: '',
|
||||
occupation: '',
|
||||
hobbies: '',
|
||||
bio: ''
|
||||
})
|
||||
|
||||
const isLoginMode = ref(true)
|
||||
const loading = ref(false)
|
||||
|
||||
const handleLogin = async () => {
|
||||
if (!form.username || !form.password) {
|
||||
ElMessage.warning('请输入用户名和密码')
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await login({
|
||||
username: form.username,
|
||||
password: form.password
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
ElMessage.success('登录成功')
|
||||
router.push('/community')
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error.message || '登录失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleRegister = async () => {
|
||||
if (!form.username || !form.password) {
|
||||
ElMessage.warning('请输入用户名和密码')
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await register({
|
||||
username: form.username,
|
||||
password: form.password,
|
||||
nickname: form.nickname,
|
||||
birthday: form.birthday,
|
||||
occupation: form.occupation,
|
||||
hobbies: form.hobbies,
|
||||
bio: form.bio
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
ElMessage.success('注册成功,已自动登录')
|
||||
// 注册成功后直接跳转到社区页面
|
||||
router.push('/community')
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error.message || '注册失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const toggleMode = () => {
|
||||
isLoginMode.value = !isLoginMode.value
|
||||
// 清空表单
|
||||
Object.keys(form).forEach(key => {
|
||||
form[key] = ''
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
padding: 40px;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
animation: slideUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.login-header h2 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #303133;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.login-header p {
|
||||
margin: 0;
|
||||
color: #909399;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
width: 100%;
|
||||
color: #409eff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.toggle-btn:hover {
|
||||
color: #66b1ff;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.login-card {
|
||||
padding: 30px 20px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.login-header h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.login-header p {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,12 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
// 添加这两行来引入 Element Plus
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
|
||||
import router from './router'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(ElementPlus) // 使用 Element Plus
|
||||
app.use(router)
|
||||
app.mount('#app')
|
||||
@ -0,0 +1,79 @@
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
})
|
||||
Binary file not shown.
Loading…
Reference in new issue