feat 搭建架构骨架
This commit is contained in:
84
src/stores/modules/cases.ts
Normal file
84
src/stores/modules/cases.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import type { BusinessType, PageResult } from '@/types/api'
|
||||
import type { Case, CaseQuery } from '@/types/business/cases'
|
||||
import { getCaseCounts, getCaseDetail, getCaseList, toggleFavorite as toggleFavoriteApi } from '@/api/cases'
|
||||
|
||||
function createDefaultQuery(): CaseQuery {
|
||||
return {
|
||||
category: 'all',
|
||||
industries: [],
|
||||
scales: [],
|
||||
dateRange: null,
|
||||
keyword: '',
|
||||
page: 1,
|
||||
pageSize: 8,
|
||||
}
|
||||
}
|
||||
|
||||
/** 案例库数据(列表 / 筛选 / 分页 / 收藏) */
|
||||
export const useCasesStore = defineStore('cases', () => {
|
||||
const query = ref<CaseQuery>(createDefaultQuery())
|
||||
const list = ref<Case[]>([])
|
||||
const total = ref(0)
|
||||
const counts = ref<Partial<Record<BusinessType, number>>>({})
|
||||
const currentCase = ref<Case | null>(null)
|
||||
|
||||
async function fetchCases(): Promise<void> {
|
||||
const result: PageResult<Case> = await getCaseList(query.value)
|
||||
list.value = result.list
|
||||
total.value = result.total
|
||||
}
|
||||
|
||||
async function fetchCounts(): Promise<void> {
|
||||
counts.value = await getCaseCounts()
|
||||
}
|
||||
|
||||
function setQuery(next: CaseQuery): void {
|
||||
query.value = next
|
||||
}
|
||||
|
||||
/** 确定筛选(重置页码后取数) */
|
||||
async function search(): Promise<void> {
|
||||
query.value = { ...query.value, page: 1 }
|
||||
await fetchCases()
|
||||
}
|
||||
|
||||
/** 重置全部条件 */
|
||||
async function resetQuery(): Promise<void> {
|
||||
query.value = createDefaultQuery()
|
||||
await fetchCases()
|
||||
}
|
||||
|
||||
async function changePage(page: number): Promise<void> {
|
||||
query.value = { ...query.value, page }
|
||||
await fetchCases()
|
||||
}
|
||||
|
||||
async function fetchCaseDetail(id: string): Promise<void> {
|
||||
currentCase.value = await getCaseDetail(id)
|
||||
}
|
||||
|
||||
async function toggleFavorite(id: string): Promise<void> {
|
||||
const updated = await toggleFavoriteApi(id)
|
||||
const index = list.value.findIndex((item) => item.id === id)
|
||||
if (index >= 0) list.value[index] = updated
|
||||
if (currentCase.value?.id === id) currentCase.value = updated
|
||||
}
|
||||
|
||||
return {
|
||||
query,
|
||||
list,
|
||||
total,
|
||||
counts,
|
||||
currentCase,
|
||||
fetchCases,
|
||||
fetchCounts,
|
||||
setQuery,
|
||||
search,
|
||||
resetQuery,
|
||||
changePage,
|
||||
fetchCaseDetail,
|
||||
toggleFavorite,
|
||||
}
|
||||
})
|
||||
79
src/stores/modules/contract.ts
Normal file
79
src/stores/modules/contract.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import type { PageResult } from '@/types/api'
|
||||
import type { Contract, ContractQuery } from '@/types/business/contract'
|
||||
import {
|
||||
getContractDetail,
|
||||
getContractList,
|
||||
getSignQr,
|
||||
refreshSignQr,
|
||||
} from '@/api/contract'
|
||||
|
||||
function createDefaultQuery(): ContractQuery {
|
||||
return {
|
||||
keyword: '',
|
||||
page: 1,
|
||||
pageSize: 8,
|
||||
}
|
||||
}
|
||||
|
||||
/** 合同中心数据(列表 / 筛选 / 分页 / 详情 / 签署二维码) */
|
||||
export const useContractStore = defineStore('contract', () => {
|
||||
const query = ref<ContractQuery>(createDefaultQuery())
|
||||
const list = ref<Contract[]>([])
|
||||
const total = ref(0)
|
||||
const current = ref<Contract | null>(null)
|
||||
const signExpires = ref(272)
|
||||
|
||||
async function fetchList(): Promise<void> {
|
||||
const result: PageResult<Contract> = await getContractList(query.value)
|
||||
list.value = result.list
|
||||
total.value = result.total
|
||||
}
|
||||
|
||||
async function fetchDetail(id: string): Promise<void> {
|
||||
current.value = await getContractDetail(id)
|
||||
}
|
||||
|
||||
function setQuery(next: ContractQuery): void {
|
||||
query.value = next
|
||||
}
|
||||
|
||||
/** 确定筛选(重置页码后取数) */
|
||||
async function search(): Promise<void> {
|
||||
query.value = { ...query.value, page: 1 }
|
||||
await fetchList()
|
||||
}
|
||||
|
||||
/** 重置全部条件 */
|
||||
async function resetQuery(): Promise<void> {
|
||||
query.value = createDefaultQuery()
|
||||
await fetchList()
|
||||
}
|
||||
|
||||
async function changePage(page: number): Promise<void> {
|
||||
query.value = { ...query.value, page }
|
||||
await fetchList()
|
||||
}
|
||||
|
||||
/** 获取/刷新签署二维码(refresh=true 时走刷新接口) */
|
||||
async function fetchSignQr(id: string, refresh = false): Promise<void> {
|
||||
const result = refresh ? await refreshSignQr(id) : await getSignQr(id)
|
||||
signExpires.value = result.expiresInSeconds
|
||||
}
|
||||
|
||||
return {
|
||||
query,
|
||||
list,
|
||||
total,
|
||||
current,
|
||||
signExpires,
|
||||
fetchList,
|
||||
fetchDetail,
|
||||
setQuery,
|
||||
search,
|
||||
resetQuery,
|
||||
changePage,
|
||||
fetchSignQr,
|
||||
}
|
||||
})
|
||||
126
src/stores/modules/demand.ts
Normal file
126
src/stores/modules/demand.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { Demand } from '@/types/business/demand'
|
||||
import type { FileInfo } from '@/types/api'
|
||||
import { formatDate } from '@/utils/format'
|
||||
import { getDraftDemand, saveDraft as saveDraftApi, submitDemand } from '@/api/demand'
|
||||
|
||||
function createDefaultForm(): Demand {
|
||||
return {
|
||||
businessType: 'internet',
|
||||
projectName: '',
|
||||
attachments: [],
|
||||
coreDescription: '',
|
||||
expectedDuration: '',
|
||||
budgetRange: '',
|
||||
userName: '王永志',
|
||||
userPhone: '1866****9618',
|
||||
remark: '',
|
||||
status: 'draft',
|
||||
}
|
||||
}
|
||||
|
||||
/** 需求提报数据(M3:表单 / 步骤 / 提交与草稿) */
|
||||
export const useDemandStore = defineStore('demand', () => {
|
||||
const form = ref<Demand>(createDefaultForm())
|
||||
const step = ref(1)
|
||||
const loading = ref(false)
|
||||
|
||||
function setStep(next: number): void {
|
||||
step.value = next
|
||||
}
|
||||
|
||||
function patchForm(partial: Partial<Demand>): void {
|
||||
form.value = { ...form.value, ...partial }
|
||||
}
|
||||
|
||||
/** File → FileInfo(mock:url 为空,uploadTime 取当前时间) */
|
||||
function addAttachment(file: File): void {
|
||||
const info: FileInfo = {
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
uploadTime: formatDate(new Date()),
|
||||
url: '',
|
||||
}
|
||||
form.value.attachments = [...form.value.attachments, info]
|
||||
}
|
||||
|
||||
function removeAttachment(target: FileInfo): void {
|
||||
form.value.attachments = form.value.attachments.filter(
|
||||
(item) => !(item.name === target.name && item.uploadTime === target.uploadTime),
|
||||
)
|
||||
}
|
||||
|
||||
/** 提交需求:必填校验通过后调 api,成功返回 true(页面可据此跳转) */
|
||||
async function submit(): Promise<boolean> {
|
||||
if (!form.value.businessType) {
|
||||
ElMessage.warning('请选择业务类型')
|
||||
return false
|
||||
}
|
||||
if (!form.value.projectName.trim()) {
|
||||
ElMessage.warning('请填写项目名称')
|
||||
return false
|
||||
}
|
||||
if (!form.value.coreDescription.trim()) {
|
||||
ElMessage.warning('请填写核心需求描述')
|
||||
return false
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
await submitDemand(form.value)
|
||||
form.value.status = 'submitted'
|
||||
ElMessage.success('提交后我们的专业顾问将在一个工作日内与您联系')
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '提交失败,请稍后重试')
|
||||
return false
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存草稿到 sessionStorage */
|
||||
async function saveDraft(): Promise<void> {
|
||||
loading.value = true
|
||||
try {
|
||||
await saveDraftApi(form.value)
|
||||
form.value.status = 'draft'
|
||||
ElMessage.success('草稿已保存')
|
||||
}
|
||||
catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '草稿保存失败')
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 恢复本地草稿(无草稿则保持默认表单) */
|
||||
async function loadDraft(): Promise<void> {
|
||||
const draft = await getDraftDemand()
|
||||
if (draft) form.value = { ...createDefaultForm(), ...draft }
|
||||
}
|
||||
|
||||
/** 重置表单与步骤 */
|
||||
function reset(): void {
|
||||
form.value = createDefaultForm()
|
||||
step.value = 1
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
step,
|
||||
loading,
|
||||
setStep,
|
||||
patchForm,
|
||||
addAttachment,
|
||||
removeAttachment,
|
||||
submit,
|
||||
saveDraft,
|
||||
loadDraft,
|
||||
reset,
|
||||
}
|
||||
})
|
||||
207
src/stores/modules/finance.ts
Normal file
207
src/stores/modules/finance.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import type { PageResult } from '@/types/api'
|
||||
import type {
|
||||
Bill,
|
||||
BillQuery,
|
||||
FinanceStats,
|
||||
Invoice,
|
||||
InvoiceQuery,
|
||||
InvoiceTitle,
|
||||
InvoiceTitlePayload,
|
||||
PayMethod,
|
||||
Payment,
|
||||
PaymentResult,
|
||||
} from '@/types/business/finance'
|
||||
import {
|
||||
applyInvoice as applyInvoiceApi,
|
||||
createInvoiceTitle,
|
||||
createPayment as createPaymentApi,
|
||||
deleteInvoiceTitle,
|
||||
exportReport as exportReportApi,
|
||||
getBillDetail,
|
||||
getBillList,
|
||||
getFinanceStats,
|
||||
getInvoiceList,
|
||||
getInvoiceTitles,
|
||||
getPayResult,
|
||||
setDefaultTitle as setDefaultTitleApi,
|
||||
switchPaymentMethod as switchPaymentMethodApi,
|
||||
updateInvoiceTitle,
|
||||
} from '@/api/finance'
|
||||
|
||||
function createDefaultBillQuery(): BillQuery {
|
||||
return { keyword: '', status: 'all', page: 1, pageSize: 6 }
|
||||
}
|
||||
|
||||
function createDefaultInvoiceQuery(): InvoiceQuery {
|
||||
return { keyword: '', status: 'all', page: 1, pageSize: 6 }
|
||||
}
|
||||
|
||||
/** 财务中心数据(统计/账单/支付/发票/抬头) */
|
||||
export const useFinanceStore = defineStore('finance', () => {
|
||||
// —— 统计
|
||||
const stats = ref<FinanceStats | null>(null)
|
||||
|
||||
// —— 账单列表
|
||||
const list = ref<Bill[]>([])
|
||||
const total = ref(0)
|
||||
const query = ref<BillQuery>(createDefaultBillQuery())
|
||||
const currentBill = ref<Bill | null>(null)
|
||||
|
||||
// —— 支付
|
||||
const payment = ref<Payment | null>(null)
|
||||
const result = ref<PaymentResult | null>(null)
|
||||
|
||||
// —— 发票
|
||||
const invoices = ref<Invoice[]>([])
|
||||
const invoiceTotal = ref(0)
|
||||
const invoiceQuery = ref<InvoiceQuery>(createDefaultInvoiceQuery())
|
||||
|
||||
// —— 发票抬头
|
||||
const titles = ref<InvoiceTitle[]>([])
|
||||
|
||||
// —— 统计
|
||||
async function fetchStats(period?: string): Promise<void> {
|
||||
stats.value = await getFinanceStats(period)
|
||||
}
|
||||
|
||||
// —— 账单
|
||||
async function fetchList(): Promise<void> {
|
||||
const pageData: PageResult<Bill> = await getBillList(query.value)
|
||||
list.value = pageData.list
|
||||
total.value = pageData.total
|
||||
}
|
||||
|
||||
function setQuery(next: BillQuery): void {
|
||||
query.value = next
|
||||
}
|
||||
|
||||
/** 确定筛选(重置页码后取数) */
|
||||
async function search(): Promise<void> {
|
||||
query.value = { ...query.value, page: 1 }
|
||||
await fetchList()
|
||||
}
|
||||
|
||||
/** 重置全部条件 */
|
||||
async function resetQuery(): Promise<void> {
|
||||
query.value = createDefaultBillQuery()
|
||||
await fetchList()
|
||||
}
|
||||
|
||||
async function changePage(page: number): Promise<void> {
|
||||
query.value = { ...query.value, page }
|
||||
await fetchList()
|
||||
}
|
||||
|
||||
async function fetchDetail(id: string): Promise<void> {
|
||||
currentBill.value = await getBillDetail(id)
|
||||
}
|
||||
|
||||
// —— 支付
|
||||
async function createPayment(billId: string, method: PayMethod): Promise<void> {
|
||||
payment.value = await createPaymentApi(billId, method)
|
||||
}
|
||||
|
||||
/** 切换支付方式(刷新二维码) */
|
||||
async function switchPaymentMethod(billId: string, method: PayMethod): Promise<void> {
|
||||
payment.value = await switchPaymentMethodApi(billId, method)
|
||||
}
|
||||
|
||||
async function fetchResult(billId: string): Promise<void> {
|
||||
result.value = await getPayResult(billId)
|
||||
}
|
||||
|
||||
// —— 发票
|
||||
async function fetchInvoices(): Promise<void> {
|
||||
const pageData: PageResult<Invoice> = await getInvoiceList(invoiceQuery.value)
|
||||
invoices.value = pageData.list
|
||||
invoiceTotal.value = pageData.total
|
||||
}
|
||||
|
||||
function setInvoiceQuery(next: InvoiceQuery): void {
|
||||
invoiceQuery.value = next
|
||||
}
|
||||
|
||||
/** 发票确定筛选(重置页码后取数) */
|
||||
async function searchInvoices(): Promise<void> {
|
||||
invoiceQuery.value = { ...invoiceQuery.value, page: 1 }
|
||||
await fetchInvoices()
|
||||
}
|
||||
|
||||
async function changeInvoicePage(page: number): Promise<void> {
|
||||
invoiceQuery.value = { ...invoiceQuery.value, page }
|
||||
await fetchInvoices()
|
||||
}
|
||||
|
||||
/** 申请开票(状态 → 开票中) */
|
||||
async function applyInvoice(id: string): Promise<void> {
|
||||
const updated = await applyInvoiceApi(id)
|
||||
const index = invoices.value.findIndex((item) => item.id === id)
|
||||
if (index >= 0) invoices.value[index] = updated
|
||||
}
|
||||
|
||||
// —— 发票抬头
|
||||
async function fetchTitles(): Promise<void> {
|
||||
titles.value = await getInvoiceTitles()
|
||||
}
|
||||
|
||||
async function createTitle(payload: InvoiceTitlePayload): Promise<void> {
|
||||
await createInvoiceTitle(payload)
|
||||
await fetchTitles()
|
||||
}
|
||||
|
||||
async function updateTitle(id: string, payload: InvoiceTitlePayload): Promise<void> {
|
||||
await updateInvoiceTitle(id, payload)
|
||||
await fetchTitles()
|
||||
}
|
||||
|
||||
async function deleteTitle(id: string): Promise<void> {
|
||||
await deleteInvoiceTitle(id)
|
||||
await fetchTitles()
|
||||
}
|
||||
|
||||
async function setDefaultTitle(id: string): Promise<void> {
|
||||
titles.value = await setDefaultTitleApi(id)
|
||||
}
|
||||
|
||||
// —— 报表导出
|
||||
async function exportReport(): Promise<void> {
|
||||
await exportReportApi()
|
||||
}
|
||||
|
||||
return {
|
||||
stats,
|
||||
list,
|
||||
total,
|
||||
query,
|
||||
currentBill,
|
||||
payment,
|
||||
result,
|
||||
invoices,
|
||||
invoiceTotal,
|
||||
invoiceQuery,
|
||||
titles,
|
||||
fetchStats,
|
||||
fetchList,
|
||||
setQuery,
|
||||
search,
|
||||
resetQuery,
|
||||
changePage,
|
||||
fetchDetail,
|
||||
createPayment,
|
||||
switchPaymentMethod,
|
||||
fetchResult,
|
||||
fetchInvoices,
|
||||
setInvoiceQuery,
|
||||
searchInvoices,
|
||||
changeInvoicePage,
|
||||
applyInvoice,
|
||||
fetchTitles,
|
||||
createTitle,
|
||||
updateTitle,
|
||||
deleteTitle,
|
||||
setDefaultTitle,
|
||||
export: exportReport,
|
||||
}
|
||||
})
|
||||
46
src/stores/modules/home.ts
Normal file
46
src/stores/modules/home.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import type { BusinessType } from '@/types/api'
|
||||
import type { Case } from '@/types/business/cases'
|
||||
import type { BusinessLine, HomeStats } from '@/types/business/home'
|
||||
import { getBusinessDetail, getBusinessLines, getFeaturedCases, getHomeStats } from '@/api/home'
|
||||
|
||||
/** 首页数据(数据层:取数 + 状态,页面只调本 store) */
|
||||
export const useHomeStore = defineStore('home', () => {
|
||||
const stats = ref<HomeStats>({ pending: 0, inProgress: 0, total: 0 })
|
||||
const businessLines = ref<BusinessLine[]>([])
|
||||
const featuredCases = ref<Case[]>([])
|
||||
const currentBusiness = ref<BusinessLine | null>(null)
|
||||
|
||||
/** 首页初始化:看板 + 业务线 + 案例精选 */
|
||||
async function fetchHomeData(): Promise<void> {
|
||||
const [homeStats, lines, cases] = await Promise.all([
|
||||
getHomeStats(),
|
||||
getBusinessLines(),
|
||||
getFeaturedCases(),
|
||||
])
|
||||
stats.value = homeStats
|
||||
businessLines.value = lines
|
||||
featuredCases.value = cases
|
||||
}
|
||||
|
||||
/** 按业务类型切换案例精选 */
|
||||
async function fetchFeaturedCases(category: BusinessType | 'all' = 'all'): Promise<void> {
|
||||
featuredCases.value = await getFeaturedCases(category)
|
||||
}
|
||||
|
||||
/** 业务详情 */
|
||||
async function fetchBusinessDetail(type: BusinessType): Promise<void> {
|
||||
currentBusiness.value = await getBusinessDetail(type)
|
||||
}
|
||||
|
||||
return {
|
||||
stats,
|
||||
businessLines,
|
||||
featuredCases,
|
||||
currentBusiness,
|
||||
fetchHomeData,
|
||||
fetchFeaturedCases,
|
||||
fetchBusinessDetail,
|
||||
}
|
||||
})
|
||||
112
src/stores/modules/message.ts
Normal file
112
src/stores/modules/message.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { PageResult } from '@/types/api'
|
||||
import type { Message, MessageQuery } from '@/types/business/message'
|
||||
import {
|
||||
getMessageDetail,
|
||||
getMessageList,
|
||||
getTabCounts,
|
||||
getUnreadCount,
|
||||
markAllRead as markAllReadApi,
|
||||
markRead as markReadApi,
|
||||
} from '@/api/message'
|
||||
|
||||
/** tab 计数结构 */
|
||||
export interface MessageTabCounts {
|
||||
all: number
|
||||
在线客服: number
|
||||
状态通知: number
|
||||
系统通知: number
|
||||
}
|
||||
|
||||
function createDefaultQuery(): MessageQuery {
|
||||
return {
|
||||
keyword: '',
|
||||
type: 'all',
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
}
|
||||
}
|
||||
|
||||
/** 消息中心数据(列表 / 筛选 / 分页 / 已读) */
|
||||
export const useMessageStore = defineStore('message', () => {
|
||||
const query = ref<MessageQuery>(createDefaultQuery())
|
||||
const list = ref<Message[]>([])
|
||||
const total = ref(0)
|
||||
const current = ref<Message | null>(null)
|
||||
const unreadCount = ref(0)
|
||||
const tabCounts = ref<MessageTabCounts>({ all: 0, 在线客服: 0, 状态通知: 0, 系统通知: 0 })
|
||||
|
||||
async function fetchList(): Promise<void> {
|
||||
const result: PageResult<Message> = await getMessageList(query.value)
|
||||
list.value = result.list
|
||||
total.value = result.total
|
||||
}
|
||||
|
||||
async function fetchDetail(id: string): Promise<void> {
|
||||
const detail = await getMessageDetail(id)
|
||||
current.value = detail
|
||||
if (detail && !detail.isRead) {
|
||||
await markReadApi(id)
|
||||
detail.isRead = true
|
||||
if (unreadCount.value > 0) unreadCount.value -= 1
|
||||
await fetchTabCounts()
|
||||
}
|
||||
}
|
||||
|
||||
function setQuery(next: MessageQuery): void {
|
||||
query.value = next
|
||||
}
|
||||
|
||||
/** 确定搜索(重置页码后取数) */
|
||||
async function search(): Promise<void> {
|
||||
query.value = { ...query.value, page: 1 }
|
||||
await fetchList()
|
||||
}
|
||||
|
||||
/** 重置全部条件 */
|
||||
async function resetQuery(): Promise<void> {
|
||||
query.value = createDefaultQuery()
|
||||
await fetchList()
|
||||
}
|
||||
|
||||
async function changePage(page: number): Promise<void> {
|
||||
query.value = { ...query.value, page }
|
||||
await fetchList()
|
||||
}
|
||||
|
||||
/** 全部标记已读 */
|
||||
async function markAllRead(): Promise<void> {
|
||||
await markAllReadApi()
|
||||
ElMessage.success('已全部标记为已读')
|
||||
await Promise.all([fetchList(), fetchUnreadCount(), fetchTabCounts()])
|
||||
if (current.value) current.value.isRead = true
|
||||
}
|
||||
|
||||
async function fetchUnreadCount(): Promise<void> {
|
||||
unreadCount.value = await getUnreadCount()
|
||||
}
|
||||
|
||||
async function fetchTabCounts(): Promise<void> {
|
||||
tabCounts.value = await getTabCounts()
|
||||
}
|
||||
|
||||
return {
|
||||
query,
|
||||
list,
|
||||
total,
|
||||
current,
|
||||
unreadCount,
|
||||
tabCounts,
|
||||
fetchList,
|
||||
fetchDetail,
|
||||
setQuery,
|
||||
search,
|
||||
resetQuery,
|
||||
changePage,
|
||||
markAllRead,
|
||||
fetchUnreadCount,
|
||||
fetchTabCounts,
|
||||
}
|
||||
})
|
||||
258
src/stores/modules/profile.ts
Normal file
258
src/stores/modules/profile.ts
Normal file
@@ -0,0 +1,258 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { BusinessType, PageResult } from '@/types/api'
|
||||
import type { Case } from '@/types/business/cases'
|
||||
import type {
|
||||
Address,
|
||||
ChangePasswordParams,
|
||||
Feedback,
|
||||
FavoritesQuery,
|
||||
HelpFaqGroup,
|
||||
ListQuery,
|
||||
Profile,
|
||||
Qualification,
|
||||
QualificationInput,
|
||||
} from '@/types/business/profile'
|
||||
import {
|
||||
bindEmail,
|
||||
bindPhone,
|
||||
changePassword,
|
||||
createAddress,
|
||||
createQualification,
|
||||
deleteAccount as deleteAccountApi,
|
||||
deleteAddress,
|
||||
deleteQualification,
|
||||
getAddresses,
|
||||
getCaseCounts,
|
||||
getFavorites,
|
||||
getHelpFaqs,
|
||||
getProfile,
|
||||
getQualifications,
|
||||
setDefaultAddress,
|
||||
submitFeedback as submitFeedbackApi,
|
||||
unbindEmail,
|
||||
updateAddress,
|
||||
updateNickname,
|
||||
updateQualification,
|
||||
verifyIdentity,
|
||||
} from '@/api/profile'
|
||||
import { storage } from '@/utils/storage'
|
||||
|
||||
function createDefaultAddressQuery(): ListQuery {
|
||||
return { page: 1, pageSize: 6 }
|
||||
}
|
||||
|
||||
function createDefaultQualificationQuery(): ListQuery {
|
||||
return { page: 1, pageSize: 6 }
|
||||
}
|
||||
|
||||
function createDefaultFavoritesQuery(): FavoritesQuery {
|
||||
return { page: 1, pageSize: 8, keyword: '', category: 'all' }
|
||||
}
|
||||
|
||||
/** 个人中心数据(资料/安全/地址/收藏/资质/反馈/帮助) */
|
||||
export const useProfileStore = defineStore('profile', () => {
|
||||
// ── 账号资料 ──
|
||||
const profile = ref<Profile | null>(null)
|
||||
|
||||
async function fetchProfile(): Promise<void> {
|
||||
profile.value = await getProfile()
|
||||
}
|
||||
|
||||
async function saveNickname(name: string): Promise<void> {
|
||||
profile.value = await updateNickname(name)
|
||||
ElMessage.success('昵称修改成功')
|
||||
}
|
||||
|
||||
// ── 账号安全 ──
|
||||
async function changePasswordAction(params: ChangePasswordParams): Promise<void> {
|
||||
await changePassword(params)
|
||||
profile.value = await getProfile()
|
||||
ElMessage.success('密码修改成功')
|
||||
}
|
||||
|
||||
async function bindPhoneAction(phone: string, code: string): Promise<void> {
|
||||
profile.value = await bindPhone(phone, code)
|
||||
ElMessage.success('手机号绑定成功')
|
||||
}
|
||||
|
||||
async function bindEmailAction(email: string, code: string): Promise<void> {
|
||||
profile.value = await bindEmail(email, code)
|
||||
ElMessage.success('邮箱绑定成功')
|
||||
}
|
||||
|
||||
async function unbindEmailAction(): Promise<void> {
|
||||
profile.value = await unbindEmail()
|
||||
ElMessage.success('已取消邮箱绑定')
|
||||
}
|
||||
|
||||
async function verifyIdentityAction(phone: string, code: string): Promise<void> {
|
||||
await verifyIdentity(phone, code)
|
||||
}
|
||||
|
||||
async function deleteAccountAction(): Promise<void> {
|
||||
await deleteAccountApi()
|
||||
storage.remove('token')
|
||||
ElMessage.success('注销成功')
|
||||
}
|
||||
|
||||
// ── 收货地址 ──
|
||||
const addresses = ref<Address[]>([])
|
||||
const addressTotal = ref(0)
|
||||
const addressQuery = ref<ListQuery>(createDefaultAddressQuery())
|
||||
|
||||
async function fetchAddresses(): Promise<void> {
|
||||
const result: PageResult<Address> = await getAddresses(addressQuery.value)
|
||||
addresses.value = result.list
|
||||
addressTotal.value = result.total
|
||||
}
|
||||
|
||||
async function createAddressAction(input: Omit<Address, 'id' | 'isDefault'>): Promise<void> {
|
||||
await createAddress(input)
|
||||
ElMessage.success('新增地址成功')
|
||||
addressQuery.value = { ...addressQuery.value, page: 1 }
|
||||
await fetchAddresses()
|
||||
}
|
||||
|
||||
async function updateAddressAction(id: string, input: Omit<Address, 'id' | 'isDefault'>): Promise<void> {
|
||||
await updateAddress(id, input)
|
||||
ElMessage.success('地址保存成功')
|
||||
await fetchAddresses()
|
||||
}
|
||||
|
||||
async function deleteAddressAction(id: string): Promise<void> {
|
||||
await deleteAddress(id)
|
||||
ElMessage.success('地址删除成功')
|
||||
await fetchAddresses()
|
||||
}
|
||||
|
||||
async function setDefaultAddressAction(id: string): Promise<void> {
|
||||
await setDefaultAddress(id)
|
||||
ElMessage.success('已设为默认地址')
|
||||
await fetchAddresses()
|
||||
}
|
||||
|
||||
async function changeAddressPage(page: number): Promise<void> {
|
||||
addressQuery.value = { ...addressQuery.value, page }
|
||||
await fetchAddresses()
|
||||
}
|
||||
|
||||
// ── 我的收藏 ──
|
||||
const favorites = ref<Case[]>([])
|
||||
const favoritesTotal = ref(0)
|
||||
const favoritesCounts = ref<Partial<Record<BusinessType, number>>>({})
|
||||
const favoritesQuery = ref<FavoritesQuery>(createDefaultFavoritesQuery())
|
||||
|
||||
async function fetchFavorites(): Promise<void> {
|
||||
const result: PageResult<Case> = await getFavorites(favoritesQuery.value)
|
||||
favorites.value = result.list
|
||||
favoritesTotal.value = result.total
|
||||
}
|
||||
|
||||
async function fetchFavoritesCounts(): Promise<void> {
|
||||
favoritesCounts.value = await getCaseCounts()
|
||||
}
|
||||
|
||||
async function searchFavorites(patch: Partial<FavoritesQuery>): Promise<void> {
|
||||
favoritesQuery.value = { ...favoritesQuery.value, ...patch, page: 1 }
|
||||
await fetchFavorites()
|
||||
}
|
||||
|
||||
async function resetFavorites(): Promise<void> {
|
||||
favoritesQuery.value = createDefaultFavoritesQuery()
|
||||
await fetchFavorites()
|
||||
}
|
||||
|
||||
async function changeFavoritesPage(page: number): Promise<void> {
|
||||
favoritesQuery.value = { ...favoritesQuery.value, page }
|
||||
await fetchFavorites()
|
||||
}
|
||||
|
||||
// ── 资质管理 ──
|
||||
const qualifications = ref<Qualification[]>([])
|
||||
const qualificationTotal = ref(0)
|
||||
const qualificationQuery = ref<ListQuery>(createDefaultQualificationQuery())
|
||||
|
||||
async function fetchQualifications(): Promise<void> {
|
||||
const result: PageResult<Qualification> = await getQualifications(qualificationQuery.value)
|
||||
qualifications.value = result.list
|
||||
qualificationTotal.value = result.total
|
||||
}
|
||||
|
||||
async function createQualificationAction(input: QualificationInput): Promise<void> {
|
||||
await createQualification(input)
|
||||
ElMessage.success('资质提交成功')
|
||||
qualificationQuery.value = { ...qualificationQuery.value, page: 1 }
|
||||
await fetchQualifications()
|
||||
}
|
||||
|
||||
async function updateQualificationAction(id: string, input: QualificationInput): Promise<void> {
|
||||
await updateQualification(id, input)
|
||||
ElMessage.success('资质保存成功')
|
||||
await fetchQualifications()
|
||||
}
|
||||
|
||||
async function deleteQualificationAction(id: string): Promise<void> {
|
||||
await deleteQualification(id)
|
||||
ElMessage.success('资质删除成功')
|
||||
await fetchQualifications()
|
||||
}
|
||||
|
||||
async function changeQualificationPage(page: number): Promise<void> {
|
||||
qualificationQuery.value = { ...qualificationQuery.value, page }
|
||||
await fetchQualifications()
|
||||
}
|
||||
|
||||
// ── 意见反馈 / 帮助中心 ──
|
||||
async function submitFeedbackAction(feedback: Feedback): Promise<void> {
|
||||
await submitFeedbackApi(feedback)
|
||||
}
|
||||
|
||||
const helpFaqs = ref<HelpFaqGroup[]>([])
|
||||
|
||||
async function fetchHelpFaqs(): Promise<void> {
|
||||
helpFaqs.value = await getHelpFaqs()
|
||||
}
|
||||
|
||||
return {
|
||||
profile,
|
||||
fetchProfile,
|
||||
saveNickname,
|
||||
changePassword: changePasswordAction,
|
||||
bindPhone: bindPhoneAction,
|
||||
bindEmail: bindEmailAction,
|
||||
unbindEmail: unbindEmailAction,
|
||||
verifyIdentity: verifyIdentityAction,
|
||||
deleteAccount: deleteAccountAction,
|
||||
addresses,
|
||||
addressTotal,
|
||||
addressQuery,
|
||||
fetchAddresses,
|
||||
createAddress: createAddressAction,
|
||||
updateAddress: updateAddressAction,
|
||||
deleteAddress: deleteAddressAction,
|
||||
setDefaultAddress: setDefaultAddressAction,
|
||||
changeAddressPage,
|
||||
favorites,
|
||||
favoritesTotal,
|
||||
favoritesCounts,
|
||||
favoritesQuery,
|
||||
fetchFavorites,
|
||||
fetchFavoritesCounts,
|
||||
searchFavorites,
|
||||
resetFavorites,
|
||||
changeFavoritesPage,
|
||||
qualifications,
|
||||
qualificationTotal,
|
||||
qualificationQuery,
|
||||
fetchQualifications,
|
||||
createQualification: createQualificationAction,
|
||||
updateQualification: updateQualificationAction,
|
||||
deleteQualification: deleteQualificationAction,
|
||||
changeQualificationPage,
|
||||
submitFeedback: submitFeedbackAction,
|
||||
helpFaqs,
|
||||
fetchHelpFaqs,
|
||||
}
|
||||
})
|
||||
106
src/stores/modules/project.ts
Normal file
106
src/stores/modules/project.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { PageResult } from '@/types/api'
|
||||
import type { AfterSalesPayload, Project, ProjectQuery } from '@/types/business/project'
|
||||
import {
|
||||
cancelAfterSales as cancelAfterSalesApi,
|
||||
deleteProjectFile,
|
||||
getProjectDetail,
|
||||
getProjectList,
|
||||
submitAfterSales as submitAfterSalesApi,
|
||||
uploadProjectFile,
|
||||
} from '@/api/project'
|
||||
|
||||
function createDefaultQuery(): ProjectQuery {
|
||||
return {
|
||||
keyword: '',
|
||||
businessType: 'all',
|
||||
status: 'all',
|
||||
timeShortcut: 'all',
|
||||
page: 1,
|
||||
pageSize: 8,
|
||||
}
|
||||
}
|
||||
|
||||
/** 项目中心数据(列表 / 筛选 / 分页 / 详情 / 文件 / 售后) */
|
||||
export const useProjectStore = defineStore('project', () => {
|
||||
const query = ref<ProjectQuery>(createDefaultQuery())
|
||||
const list = ref<Project[]>([])
|
||||
const total = ref(0)
|
||||
const current = ref<Project | null>(null)
|
||||
|
||||
async function fetchList(): Promise<void> {
|
||||
const result: PageResult<Project> = await getProjectList(query.value)
|
||||
list.value = result.list
|
||||
total.value = result.total
|
||||
}
|
||||
|
||||
async function fetchDetail(id: string): Promise<void> {
|
||||
current.value = await getProjectDetail(id)
|
||||
}
|
||||
|
||||
function setQuery(next: ProjectQuery): void {
|
||||
query.value = next
|
||||
}
|
||||
|
||||
/** 确定筛选(重置页码后取数) */
|
||||
async function search(): Promise<void> {
|
||||
query.value = { ...query.value, page: 1 }
|
||||
await fetchList()
|
||||
}
|
||||
|
||||
/** 重置全部条件 */
|
||||
async function resetQuery(): Promise<void> {
|
||||
query.value = createDefaultQuery()
|
||||
await fetchList()
|
||||
}
|
||||
|
||||
async function changePage(page: number): Promise<void> {
|
||||
query.value = { ...query.value, page }
|
||||
await fetchList()
|
||||
}
|
||||
|
||||
/** 上传项目文件(追加到当前详情) */
|
||||
async function uploadFile(file: File): Promise<void> {
|
||||
const info = await uploadProjectFile(file)
|
||||
current.value?.files.push(info)
|
||||
}
|
||||
|
||||
/** 删除当前详情中的项目文件 */
|
||||
async function deleteFile(index: number): Promise<void> {
|
||||
await deleteProjectFile(index)
|
||||
current.value?.files.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 提交售后工单 */
|
||||
async function submitAfterSales(payload: AfterSalesPayload): Promise<void> {
|
||||
if (!current.value) return
|
||||
current.value.afterSales = await submitAfterSalesApi(current.value.id, payload)
|
||||
ElMessage.success('已提交售后工单')
|
||||
}
|
||||
|
||||
/** 取消售后工单 */
|
||||
async function cancelAfterSales(): Promise<void> {
|
||||
if (!current.value) return
|
||||
current.value.afterSales = await cancelAfterSalesApi(current.value.id)
|
||||
ElMessage.success('已取消售后工单')
|
||||
}
|
||||
|
||||
return {
|
||||
query,
|
||||
list,
|
||||
total,
|
||||
current,
|
||||
fetchList,
|
||||
fetchDetail,
|
||||
setQuery,
|
||||
search,
|
||||
resetQuery,
|
||||
changePage,
|
||||
uploadFile,
|
||||
deleteFile,
|
||||
submitAfterSales,
|
||||
cancelAfterSales,
|
||||
}
|
||||
})
|
||||
60
src/stores/modules/service.ts
Normal file
60
src/stores/modules/service.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import type { ChatMessage } from '@/types/business/service'
|
||||
import {
|
||||
getChatHistory,
|
||||
mockAutoReply,
|
||||
sendChatMessage,
|
||||
uploadChatFile,
|
||||
} from '@/api/service'
|
||||
|
||||
/** 在线客服会话数据(消息列表 / 发送状态) */
|
||||
export const useServiceStore = defineStore('service', () => {
|
||||
const messages = ref<ChatMessage[]>([])
|
||||
const sending = ref(false)
|
||||
|
||||
async function fetchHistory(): Promise<void> {
|
||||
messages.value = await getChatHistory()
|
||||
}
|
||||
|
||||
/** 发送文本:先推入用户消息,600ms 后推入客服自动回复 */
|
||||
async function send(content: string): Promise<void> {
|
||||
const trimmed = content.trim()
|
||||
if (!trimmed || sending.value) return
|
||||
sending.value = true
|
||||
try {
|
||||
const userMsg = await sendChatMessage(trimmed)
|
||||
messages.value.push(userMsg)
|
||||
window.setTimeout(() => {
|
||||
void mockAutoReply().then((reply) => {
|
||||
messages.value.push(reply)
|
||||
sending.value = false
|
||||
})
|
||||
}, 600)
|
||||
}
|
||||
catch {
|
||||
sending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 发送文件卡片消息 */
|
||||
async function sendFile(file: File): Promise<void> {
|
||||
if (sending.value) return
|
||||
sending.value = true
|
||||
try {
|
||||
const fileMsg = await uploadChatFile(file)
|
||||
messages.value.push(fileMsg)
|
||||
}
|
||||
finally {
|
||||
sending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
messages,
|
||||
sending,
|
||||
fetchHistory,
|
||||
send,
|
||||
sendFile,
|
||||
}
|
||||
})
|
||||
24
src/stores/modules/site.ts
Normal file
24
src/stores/modules/site.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
/** 站点公共数据(站点配置、全局 loading),非页面级 */
|
||||
export const useSiteStore = defineStore('site', () => {
|
||||
const globalLoading = ref(false)
|
||||
|
||||
// TODO(api): 站点配置接入后由 fetchSiteConfig 覆盖默认值
|
||||
const siteInfo = ref({
|
||||
appName: '虫子智造',
|
||||
hotline: '400-XXX-XXXX',
|
||||
businessEmail: 'business@chongzizhizao.com',
|
||||
supportEmail: 'support@chongzizhizao.com',
|
||||
workTime: '周一-周五 9:00-18:00',
|
||||
icp: 'ICP 备 XXXXXXXX 号',
|
||||
police: '公网安备 XXXXXXXXXXXX 号',
|
||||
})
|
||||
|
||||
function setGlobalLoading(value: boolean): void {
|
||||
globalLoading.value = value
|
||||
}
|
||||
|
||||
return { globalLoading, siteInfo, setGlobalLoading }
|
||||
})
|
||||
137
src/stores/modules/user.ts
Normal file
137
src/stores/modules/user.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type {
|
||||
LoginByAccountParams,
|
||||
LoginBySmsParams,
|
||||
LoginResult,
|
||||
RegisterParams,
|
||||
ResetPasswordParams,
|
||||
User,
|
||||
} from '@/types/business/user'
|
||||
import {
|
||||
loginByAccount as loginByAccountApi,
|
||||
loginBySms as loginBySmsApi,
|
||||
register as registerApi,
|
||||
resetPassword as resetPasswordApi,
|
||||
sendSmsCode as sendSmsCodeApi,
|
||||
} from '@/api/auth'
|
||||
import { storage } from '@/utils/storage'
|
||||
|
||||
/** 用户登录态(M4:token 持久化 key 'token',路由守卫读取同一 key) */
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
const token = ref<string | null>(storage.get<string>('token'))
|
||||
const user = ref<User | null>(null)
|
||||
const loading = ref(false)
|
||||
|
||||
const isLoggedIn = computed(() => Boolean(token.value))
|
||||
|
||||
/** 发送短信验证码 */
|
||||
async function sendSmsCode(phone: string): Promise<void> {
|
||||
try {
|
||||
await sendSmsCodeApi(phone)
|
||||
ElMessage.success('验证码已发送')
|
||||
}
|
||||
catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '验证码发送失败')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/** 登录成功统一落库(token 持久化) */
|
||||
function applyLogin(result: LoginResult): void {
|
||||
token.value = result.token
|
||||
user.value = result.user
|
||||
storage.set('token', result.token)
|
||||
}
|
||||
|
||||
/** 账号密码登录 */
|
||||
async function loginByAccount(params: LoginByAccountParams): Promise<boolean> {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await loginByAccountApi(params)
|
||||
applyLogin(result)
|
||||
ElMessage.success('登录成功')
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '登录失败')
|
||||
return false
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 手机验证码登录 */
|
||||
async function loginBySms(params: LoginBySmsParams): Promise<boolean> {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await loginBySmsApi(params)
|
||||
applyLogin(result)
|
||||
ElMessage.success('登录成功')
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '登录失败')
|
||||
return false
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 注册 */
|
||||
async function register(params: RegisterParams): Promise<boolean> {
|
||||
loading.value = true
|
||||
try {
|
||||
await registerApi(params)
|
||||
ElMessage.success('注册成功,请登录')
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '注册失败')
|
||||
return false
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置密码 */
|
||||
async function resetPassword(params: ResetPasswordParams): Promise<boolean> {
|
||||
loading.value = true
|
||||
try {
|
||||
await resetPasswordApi(params)
|
||||
ElMessage.success('修改成功,请登录')
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '修改失败')
|
||||
return false
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 退出登录(清除持久化 token) */
|
||||
function logout(): void {
|
||||
token.value = null
|
||||
user.value = null
|
||||
storage.remove('token')
|
||||
}
|
||||
|
||||
return {
|
||||
token,
|
||||
user,
|
||||
loading,
|
||||
isLoggedIn,
|
||||
sendSmsCode,
|
||||
loginByAccount,
|
||||
loginBySms,
|
||||
register,
|
||||
resetPassword,
|
||||
logout,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user