Browse Source

代码

master
“wangzihua” 4 months ago
parent
commit
9b570ff406
  1. 3
      .env.development
  2. 3
      .env.localhost
  3. 3
      .env.pre
  4. 3
      .env.production
  5. 3
      .env.test
  6. 18
      .eslintignore
  7. 66
      .eslintrc.cjs
  8. 7
      .gitignore
  9. 30
      .prettierrc.cjs
  10. 3
      .stylelintignore
  11. 70
      .stylelintrc.js
  12. 3
      .vscode/settings.json
  13. 17
      src/api/business/service-applications/service-applications-api.js
  14. 2
      src/api/support/file-api.js
  15. 2
      src/components/support/file-upload/index.vue
  16. 235
      src/components/system/service-count/quarter-statistics.vue
  17. 30
      src/views/business/erp/service/service-application-count.vue
  18. 59
      src/views/business/erp/service/service-applications-form.vue
  19. 102
      src/views/business/erp/service/service-applications-list.vue

3
.env.development

@ -0,0 +1,3 @@
NODE_ENV=development
VITE_APP_TITLE='律师公益法律服务活动申报与管理平台'
VITE_APP_API_URL='http://127.0.0.1:1024'

3
.env.localhost

@ -0,0 +1,3 @@
NODE_ENV=development
VITE_APP_TITLE='律师公益法律服务活动申报与管理平台'
VITE_APP_API_URL='http://127.0.0.1:1024'

3
.env.pre

@ -0,0 +1,3 @@
NODE_ENV=production
VITE_APP_TITLE='律师公益法律服务活动申报与管理平台'
VITE_APP_API_URL='https://preview.smartadmin.vip/smart-admin-api'

3
.env.production

@ -0,0 +1,3 @@
NODE_ENV=production
VITE_APP_TITLE='律师公益法律服务活动申报与管理平台'
VITE_APP_API_URL='https://preview.smartadmin.vip/smart-admin-api'

3
.env.test

@ -0,0 +1,3 @@
NODE_ENV=production
VITE_APP_TITLE='律师公益法律服务活动申报与管理平台'
VITE_APP_API_URL='http://127.0.0.1:1024'

18
.eslintignore

@ -0,0 +1,18 @@
*.sh
node_modules
lib
*.md
*.woff
*.ttf
.vscode
.idea
dist
public
/docs
.husky
.local
.localhost
/bin
Dockerfile
src/assets

66
.eslintrc.cjs

@ -0,0 +1,66 @@
/*
* @Description:
* @Author: zhuoda
* @Date: 2021-11-05
* @LastEditTime: 2022-07-05
* @LastEditors: zhuoda
*/
module.exports = {
root: true, //此项是用来告诉eslint找当前配置文件不能往父级查找
env: {
browser: true,
es2021: true,
node: true,
},
parser: 'vue-eslint-parser', //使用vue-eslint-parser 来解析vue文件中的 template和script
parserOptions: {
ecmaVersion: 12, // 默认情况下,ESLint使用的是ECMAScript5语法,此处我们设置的选项是 es12
sourceType: 'module', // 指定js导入的方式
},
extends: ['plugin:vue/vue3-essential', 'eslint:recommended', 'plugin:vue/base'],
globals: {
defineProps: 'readonly',
defineEmits: 'readonly',
defineExpose: 'readonly',
withDefaults: 'readonly',
},
plugins: ['vue'],
rules: {
'no-unused-vars': [
'error',
// we are only using this rule to check for unused arguments since TS
// catches unused variables but not args.
{ varsIgnorePattern: '.*', args: 'none' },
],
'space-before-function-paren': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': 'off',
'vue/multi-word-component-names': [
'error',
{
ignores: ['index'], //需要忽略的组件名
},
],
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'never',
component: 'always',
},
svg: 'always',
math: 'always',
},
],
// Enable vue/script-setup-uses-vars rule
'vue/script-setup-uses-vars': 'error',
},
};

7
.gitignore

@ -0,0 +1,7 @@
node_modules
.DS_Store
**/.DS_Store
dist
dist-ssr
*.local
.idea

30
.prettierrc.cjs

@ -0,0 +1,30 @@
/*
* 代码格式化配置
*
* @Author: 1024创新实验室-主任:卓大
* @Date: 2022-09-12 14:44:18
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
*/
module.exports = {
printWidth: 150, // 每行代码长度(默认80)
tabWidth: 2, // 缩进空格数
useTabs: false, //不用tab缩进
semi: true, //// 在语句末尾打印分号
singleQuote: true, // 使用单引号而不是双引号
vueIndentScriptAndStyle: true, //Vue文件脚本和样式标签缩进
quoteProps: 'as-needed', // 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
jsxSingleQuote: true, // 在JSX中使用单引号而不是双引号
trailingComma: 'es5', //多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
bracketSpacing: true, // 在对象文字中的括号之间打印空格
jsxBracketSameLine: false, //jsx 标签的反尖括号需要换行
arrowParens: 'always', // 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
rangeStart: 0, // 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
rangeEnd: Infinity,
requirePragma: false, // 指定要使用的解析器,不需要写文件开头的 @prettier
insertPragma: false, // 不需要自动在文件开头插入 @prettier
proseWrap: 'preserve', // 使用默认的折行标准 always\never\preserve
htmlWhitespaceSensitivity: 'css', // 指定HTML文件的全局空格敏感度 css\strict\ignore
endOfLine: 'auto', // 因为prettier的规范和eslint的换行规则不同,所以这个必须配置。要不然每次打开文件都会有一堆的警告;换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr
};

3
.stylelintignore

@ -0,0 +1,3 @@
/dist/*
/public/*
public/*

70
.stylelintrc.js

@ -0,0 +1,70 @@
module.exports = {
root: true,
plugins: ['stylelint-order'],
extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
rules: {
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['global'],
},
],
'selector-pseudo-element-no-unknown': [
true,
{
ignorePseudoElements: ['v-deep'],
},
],
'at-rule-no-unknown': [
true,
{
ignoreAtRules: [
'tailwind',
'apply',
'variants',
'responsive',
'screen',
'function',
'if',
'each',
'include',
'mixin',
],
},
],
'no-empty-source': null,
'named-grid-areas-no-invalid': null,
'unicode-bom': 'never',
'no-descending-specificity': null,
'font-family-no-missing-generic-family-keyword': null,
'declaration-colon-space-after': 'always-single-line',
'declaration-colon-space-before': 'never',
// 'declaration-block-trailing-semicolon': 'always',
'rule-empty-line-before': [
'always',
{
ignore: ['after-comment', 'first-nested'],
},
],
'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }],
'order/order': [
[
'dollar-variables',
'custom-properties',
'at-rules',
'declarations',
{
type: 'at-rule',
name: 'supports',
},
{
type: 'at-rule',
name: 'media',
},
'rules',
],
{ severity: 'warning' },
],
},
ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
};

3
.vscode/settings.json

@ -0,0 +1,3 @@
{
"search.useGlobalIgnoreFiles": true
}

17
src/api/business/service-applications/service-applications-api.js

@ -30,10 +30,23 @@ export const serviceApplicationsApi = {
return postRequest('/serviceApplications/update', param); return postRequest('/serviceApplications/update', param);
}, },
/** /**
* 提交 @author wzh * 编辑提交 @author wzh
*/ */
submit: (id) => { submit: (id) => {
return postRequest(`/serviceApplications/submit/${id}`); return getRequest(`/serviceApplications/submit/${id}`);
},
/**
* 审核 @author wzh
*/
review: (param) => {
return postRequest('/serviceApplications/review', param);
},
/**
* 新增提交 @author wzh
*/
addSubmit: (param) => {
return postRequest('/serviceApplications/addSubmit', param);
}, },
/** /**
* 批量提交 @author wzh * 批量提交 @author wzh

2
src/api/support/file-api.js

@ -40,6 +40,6 @@ export const fileApi = {
* 根据文件ID列表获取文件信息 @author 系统 * 根据文件ID列表获取文件信息 @author 系统
*/ */
getFileList: (fileIds) => { getFileList: (fileIds) => {
return getRequest(`/support/file/getFileList?fileIds=${fileIds}`); return postRequest('/support/file/getFileList', { fileIds });
}, },
}; };

2
src/components/support/file-upload/index.vue

@ -95,7 +95,7 @@
}); });
// //
const imgFileType = ['jpg', 'jpeg', 'png', 'gif', 'PNG', 'GIF', 'JPG']; const imgFileType = ['jpg', 'jpeg', 'png', 'gif','.PNG','.GIF','.JPG'];
// //
const files = computed(() => { const files = computed(() => {

235
src/components/system/service-count/quarter-statistics.vue

@ -0,0 +1,235 @@
<!--
* 季度统计组件
*
* @Author: wzh
* @Date: 2025-12-24 14:44:06
* @Copyright 1.0
-->
<template>
<div class="quarter-statistics">
<!-- 查询表单 -->
<a-card title="查询条件" size="small" class="query-card">
<a-form :model="queryForm" layout="inline">
<a-form-item label="季度">
<a-select v-model:value="queryForm.quarter" placeholder="请选择季度" style="width: 120px">
<a-select-option v-for="quarter in quarterOptions" :key="quarter.value" :value="quarter.value">
{{ quarter.label }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="年度">
<a-select v-model:value="queryForm.year" placeholder="请选择年度" style="width: 120px">
<a-select-option v-for="year in yearOptions" :key="year" :value="year">
{{ year }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="律师姓名">
<a-input v-model:value="queryForm.lawyerName" placeholder="请输入律师姓名" style="width: 150px" />
</a-form-item>
<a-form-item label="律所名称">
<a-input v-model:value="queryForm.firmName" placeholder="请输入律所名称" style="width: 150px" />
</a-form-item>
<a-form-item>
<a-button type="primary" @click="handleSearch">
<SearchOutlined />
查询
</a-button>
<a-button @click="handleReset" style="margin-left: 8px">
<ReloadOutlined />
重置
</a-button>
<a-button @click="handleExport" type="primary" style="margin-left: 8px">
<ExportOutlined />
导出Excel
</a-button>
</a-form-item>
</a-form>
</a-card>
<!-- 统计表格 -->
<a-card title="季度服务统计" size="small" class="table-card">
<a-table
:columns="columns"
:dataSource="tableData"
:pagination="pagination"
:loading="loading"
size="small"
bordered
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'quarterDuration'">
{{ record.quarterDuration }} 小时
</template>
<template v-else-if="column.dataIndex === 'quarterCost'">
{{ record.quarterCost }}
</template>
<template v-else-if="column.dataIndex === 'yearDuration'">
{{ record.yearDuration }} 小时
</template>
<template v-else-if="column.dataIndex === 'yearCost'">
{{ record.yearCost }}
</template>
</template>
</a-table>
</a-card>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { SearchOutlined, ReloadOutlined, ExportOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import { serviceApplicationsApi } from '/@/api/business/service-applications/service-applications-api';
//
const queryForm = reactive({
quarter: undefined,
year: new Date().getFullYear(),
lawyerName: '',
firmName: ''
});
//
const quarterOptions = [
{ label: '第一季度', value: 1 },
{ label: '第二季度', value: 2 },
{ label: '第三季度', value: 3 },
{ label: '第四季度', value: 4 }
];
// 5
const yearOptions = ref([]);
//
const columns = [
{
title: '执业律师姓名',
dataIndex: 'lawyerName',
key: 'lawyerName',
width: 120
},
{
title: '执业证号',
dataIndex: 'certificateNumber',
key: 'certificateNumber',
width: 120
},
{
title: '季度累计服务时长',
dataIndex: 'quarterDuration',
key: 'quarterDuration',
width: 120
},
{
title: '季度累计服务成本',
dataIndex: 'quarterCost',
key: 'quarterCost',
width: 120
},
{
title: '年度累计服务时长',
dataIndex: 'yearDuration',
key: 'yearDuration',
width: 120
},
{
title: '年度累计服务成本',
dataIndex: 'yearCost',
key: 'yearCost',
width: 120
}
];
const tableData = ref([]);
const loading = ref(false);
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`
});
//
function initYearOptions() {
const currentYear = new Date().getFullYear();
for (let i = currentYear; i >= currentYear - 4; i--) {
yearOptions.value.push(i);
}
}
//
async function handleSearch() {
loading.value = true;
try {
const params = {
...queryForm,
pageNum: pagination.current,
pageSize: pagination.pageSize
};
// API
const result = await serviceApplicationsApi.getQuarterStatistics(params);
if (result.data) {
tableData.value = result.data.list || [];
pagination.total = result.data.total || 0;
}
} catch (error) {
message.error('查询失败');
console.error(error);
} finally {
loading.value = false;
}
}
//
function handleReset() {
Object.assign(queryForm, {
quarter: undefined,
year: new Date().getFullYear(),
lawyerName: '',
firmName: ''
});
handleSearch();
}
// Excel
function handleExport() {
message.info('导出功能开发中...');
// API
// await serviceApplicationsApi.exportQuarterStatistics(queryForm);
}
//
function handleTableChange(pag) {
pagination.current = pag.current;
pagination.pageSize = pag.pageSize;
handleSearch();
}
onMounted(() => {
initYearOptions();
handleSearch();
});
</script>
<style scoped>
.quarter-statistics {
padding: 0;
}
.query-card {
margin-bottom: 16px;
}
.table-card {
margin-bottom: 0;
}
</style>

30
src/views/business/erp/service/service-application-count.vue

@ -0,0 +1,30 @@
<!--
* 服务申报统计
*
* @Author: wzh
* @Date: 2025-12-24 14:44:06
* @Copyright 1.0
-->
<template>
<div class="service-application-count">
<a-tabs v-model:activeKey="activeTab" type="card">
<!-- 季度统计 -->
<a-tab-pane key="quarter" tab="季度统计">
<QuarterStatistics />
</a-tab-pane>
</a-tabs>
</div>
</template>
<script setup>
import { ref } from 'vue';
import QuarterStatistics from '/@/components/system/service-count/quarter-statistics.vue';
const activeTab = ref('quarter');
</script>
<style scoped>
.service-application-count {
padding: 16px;
background: #fff;
}
</style>

59
src/views/business/erp/service/service-applications-form.vue

@ -149,7 +149,7 @@
<template #footer> <template #footer>
<a-space> <a-space>
<a-button @click="onClose">取消</a-button> <a-button @click="onClose">取消</a-button>
<a-button type="primary" @click="onSubmit">保存</a-button> <a-button type="primary" @click="onSave">保存</a-button>
<a-button type="primary" @click="onSubmit">提交</a-button> <a-button type="primary" @click="onSubmit">提交</a-button>
</a-space> </a-space>
</template> </template>
@ -448,8 +448,8 @@
serviceContent: [{ required: true, message: '服务内容描述 必填' }], serviceContent: [{ required: true, message: '服务内容描述 必填' }],
}; };
// //
async function onSubmit() { async function onSave() {
try { try {
// //
const editorContent = serviceContentRef.value?.getHtml() || ''; const editorContent = serviceContentRef.value?.getHtml() || '';
@ -466,7 +466,25 @@
} }
} }
// API //
async function onSubmit() {
try {
//
const editorContent = serviceContentRef.value?.getHtml() || '';
const cleanContent = editorContent.replace(/<[^>]*>/g, '').trim();
if (!cleanContent) {
message.error('服务内容描述 必填');
return;
}
await formRef.value.validateFields();
submit();
} catch (err) {
message.error('参数验证错误,请仔细填写表单数据!');
}
}
// API
async function save() { async function save() {
SmartLoading.show(); SmartLoading.show();
try { try {
@ -479,11 +497,42 @@
} }
if (form.applicationId) { if (form.applicationId) {
// update
await serviceApplicationsApi.update(submitData); await serviceApplicationsApi.update(submitData);
} else { } else {
// add
await serviceApplicationsApi.add(submitData); await serviceApplicationsApi.add(submitData);
} }
message.success('操作成功'); message.success('保存成功');
emits('reloadList');
onClose();
} catch (err) {
smartSentry.captureError(err);
} finally {
SmartLoading.hide();
}
}
// API
async function submit() {
SmartLoading.show();
try {
//
const submitData = { ...form };
// attachmentIds
if (!submitData.attachmentIds) {
submitData.attachmentIds = '';
}
if (form.applicationId) {
// submitapplicationId
await serviceApplicationsApi.submit(form.applicationId);
} else {
// addSubmit
await serviceApplicationsApi.addSubmit(submitData);
}
message.success('提交成功');
emits('reloadList'); emits('reloadList');
onClose(); onClose();
} catch (err) { } catch (err) {

102
src/views/business/erp/service/service-applications-list.vue

@ -110,8 +110,9 @@
<template v-if="column.dataIndex === 'action'"> <template v-if="column.dataIndex === 'action'">
<div class="smart-table-operate"> <div class="smart-table-operate">
<a-button @click="showForm(record)" type="link">编辑</a-button> <a-button v-if="record.firmAuditStatus === 0 || record.firmAuditStatus === 4" @click="showForm(record)" type="link">编辑</a-button>
<a-button v-if="record.firmAuditStatus === 0" @click="onSubmit(record)" type="link">提交</a-button> <a-button v-if="record.firmAuditStatus === 0" @click="onSubmit(record)" type="link">提交</a-button>
<a-button v-if="record.firmAuditStatus === 1 || record.firmAuditStatus === 2" @click="showAuditModal(record)" type="link">审核</a-button>
<a-button @click="onDelete(record)" danger type="link">删除</a-button> <a-button @click="onDelete(record)" danger type="link">删除</a-button>
</div> </div>
</template> </template>
@ -119,7 +120,30 @@
</a-table> </a-table>
<!---------- 表格 end -----------> <!---------- 表格 end ----------->
<div class="smart-query-table-page"> <!---------- 审核弹框 begin ----------->
<a-modal
v-model:visible="auditModalVisible"
title="服务申报审核"
:confirm-loading="auditLoading"
@ok="handleAudit"
@cancel="handleAuditCancel"
width="400px"
>
<div style="text-align: center; padding: 20px 0;">
<p style="margin-bottom: 20px; font-size: 16px;">请选择审核结果</p>
<a-radio-group v-model:value="auditForm.auditResult" size="large">
<a-radio :value="3" style="margin-right: 30px;">
<span style="font-size: 16px;">同意</span>
</a-radio>
<a-radio :value="4">
<span style="font-size: 16px;">拒绝</span>
</a-radio>
</a-radio-group>
</div>
</a-modal>
<!---------- 审核弹框 end ----------->
<div class="smart-query-table-page">
<a-pagination <a-pagination
showSizeChanger showSizeChanger
showQuickJumper showQuickJumper
@ -282,9 +306,18 @@
const tableLoading = ref(false); const tableLoading = ref(false);
// //
const tableData = ref([]); const tableData = ref([]);
// //
const employeeList = ref([]); const employeeList = ref([]);
//
const auditModalVisible = ref(false);
const auditLoading = ref(false);
const currentAuditRecord = ref(null);
const auditForm = reactive({
auditResult: 3, //
auditRemark: ''
});
// //
async function loadAllEmployees() { async function loadAllEmployees() {
@ -385,22 +418,65 @@
} }
// //
async function requestDelete(data){ async function requestDelete(data){
SmartLoading.show(); SmartLoading.show();
try {
let deleteForm = {
goodsIdList: selectedRowKeyList.value,
};
await serviceApplicationsApi.delete(data.applicationId);
message.success('删除成功');
queryData();
} catch (e) {
smartSentry.captureError(e);
} finally {
SmartLoading.hide();
}
}
//
function showAuditModal(record) {
currentAuditRecord.value = record;
auditForm.auditResult = 3; //
auditForm.auditRemark = '';
auditModalVisible.value = true;
}
//
async function handleAudit() {
if (!currentAuditRecord.value) {
message.error('未选择审核记录');
return;
}
auditLoading.value = true;
const auditData = {
applicationId: currentAuditRecord.value.applicationId,
firmAuditStatus: auditForm.auditResult
};
try { try {
let deleteForm = { await serviceApplicationsApi.review(auditData);
goodsIdList: selectedRowKeyList.value, message.success('审核成功');
}; auditModalVisible.value = false;
await serviceApplicationsApi.delete(data.applicationId);
message.success('删除成功');
queryData(); queryData();
} catch (e) { } catch (error) {
smartSentry.captureError(e); message.error('审核失败');
console.error('审核失败:', error);
} finally { } finally {
SmartLoading.hide(); auditLoading.value = false;
} }
} }
//
function handleAuditCancel() {
auditModalVisible.value = false;
currentAuditRecord.value = null;
auditForm.auditResult = 3;
auditForm.auditRemark = '';
}
// ---------------------------- ---------------------------- // ---------------------------- ----------------------------
// //

Loading…
Cancel
Save